Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/db/development/create_base_work_item_types_spec.rb9
-rw-r--r--spec/db/production/create_base_work_item_types_spec.rb9
-rw-r--r--spec/frontend/deploy_freeze/store/actions_spec.js5
-rw-r--r--spec/frontend/lib/logger/index_spec.js23
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb22
-rw-r--r--spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb59
-rw-r--r--spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb7
-rw-r--r--spec/lib/gitlab/database_importers/work_items/base_type_importer_spec.rb9
-rw-r--r--spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb48
-rw-r--r--spec/lib/gitlab/pagination/keyset/cursor_based_request_context_spec.rb28
-rw-r--r--spec/lib/gitlab/pagination/keyset/cursor_pager_spec.rb6
-rw-r--r--spec/migrations/20210831203408_upsert_base_work_item_types_spec.rb48
-rw-r--r--spec/models/ci/pipeline_variable_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb28
-rw-r--r--spec/requests/api/groups_spec.rb121
-rw-r--r--spec/support/shared_examples/work_item_base_types_importer.rb7
16 files changed, 381 insertions, 50 deletions
diff --git a/spec/db/development/create_base_work_item_types_spec.rb b/spec/db/development/create_base_work_item_types_spec.rb
new file mode 100644
index 00000000000..914b84d8668
--- /dev/null
+++ b/spec/db/development/create_base_work_item_types_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Create base work item types in development' do
+ subject { load Rails.root.join('db', 'fixtures', 'development', '001_create_base_work_item_types.rb') }
+
+ it_behaves_like 'work item base types importer'
+end
diff --git a/spec/db/production/create_base_work_item_types_spec.rb b/spec/db/production/create_base_work_item_types_spec.rb
new file mode 100644
index 00000000000..81d80104bb4
--- /dev/null
+++ b/spec/db/production/create_base_work_item_types_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Create base work item types in production' do
+ subject { load Rails.root.join('db', 'fixtures', 'production', '003_create_base_work_item_types.rb') }
+
+ it_behaves_like 'work item base types importer'
+end
diff --git a/spec/frontend/deploy_freeze/store/actions_spec.js b/spec/frontend/deploy_freeze/store/actions_spec.js
index ac606f1b182..ad67afdce75 100644
--- a/spec/frontend/deploy_freeze/store/actions_spec.js
+++ b/spec/frontend/deploy_freeze/store/actions_spec.js
@@ -5,6 +5,7 @@ import * as actions from '~/deploy_freeze/store/actions';
import * as types from '~/deploy_freeze/store/mutation_types';
import getInitialState from '~/deploy_freeze/store/state';
import createFlash from '~/flash';
+import * as logger from '~/lib/logger';
import axios from '~/lib/utils/axios_utils';
import { freezePeriodsFixture, timezoneDataFixture } from '../helpers';
@@ -218,7 +219,7 @@ describe('deploy freeze store actions', () => {
});
it('should show flash error and set error in state on delete failure', () => {
- const errorSpy = jest.spyOn(console, 'error').mockImplementation();
+ jest.spyOn(logger, 'logError').mockImplementation();
const error = new Error();
Api.deleteFreezePeriod.mockRejectedValue(error);
@@ -234,7 +235,7 @@ describe('deploy freeze store actions', () => {
() => {
expect(createFlash).toHaveBeenCalled();
- expect(errorSpy).toHaveBeenCalledWith('[gitlab] Unable to delete deploy freeze:', error);
+ expect(logger.logError).toHaveBeenCalledWith('Unable to delete deploy freeze', error);
},
);
});
diff --git a/spec/frontend/lib/logger/index_spec.js b/spec/frontend/lib/logger/index_spec.js
new file mode 100644
index 00000000000..9382fafe4de
--- /dev/null
+++ b/spec/frontend/lib/logger/index_spec.js
@@ -0,0 +1,23 @@
+import { logError, LOG_PREFIX } from '~/lib/logger';
+
+describe('~/lib/logger', () => {
+ let consoleErrorSpy;
+
+ beforeEach(() => {
+ consoleErrorSpy = jest.spyOn(console, 'error');
+ consoleErrorSpy.mockImplementation();
+ });
+
+ describe('logError', () => {
+ it('sends given message to console.error', () => {
+ const message = 'Lorem ipsum dolar sit amit';
+ const error = new Error('lorem ipsum');
+
+ expect(consoleErrorSpy).not.toHaveBeenCalled();
+
+ logError(message, error);
+
+ expect(consoleErrorSpy).toHaveBeenCalledWith(LOG_PREFIX, `${message}\n`, error);
+ });
+ });
+});
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index d752496d809..719c14ee188 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe Resolvers::IssuesResolver do
let_it_be(:issue4) { create(:issue) }
let_it_be(:label1) { create(:label, project: project) }
let_it_be(:label2) { create(:label, project: project) }
+ let_it_be(:upvote_award) { create(:award_emoji, :upvote, user: current_user, awardable: issue1) }
specify do
expect(described_class).to have_nullable_graphql_type(Types::IssueType.connection_type)
@@ -200,6 +201,27 @@ RSpec.describe Resolvers::IssuesResolver do
end
end
+ context 'filtering by reaction emoji' do
+ let_it_be(:downvoted_issue) { create(:issue, project: project) }
+ let_it_be(:downvote_award) { create(:award_emoji, :downvote, user: current_user, awardable: downvoted_issue) }
+
+ it 'filters by reaction emoji' do
+ expect(resolve_issues(my_reaction_emoji: upvote_award.name)).to contain_exactly(issue1)
+ end
+
+ it 'filters by reaction emoji wildcard "none"' do
+ expect(resolve_issues(my_reaction_emoji: 'none')).to contain_exactly(issue2)
+ end
+
+ it 'filters by reaction emoji wildcard "any"' do
+ expect(resolve_issues(my_reaction_emoji: 'any')).to contain_exactly(issue1, downvoted_issue)
+ end
+
+ it 'filters by negated reaction emoji' do
+ expect(resolve_issues(not: { my_reaction_emoji: downvote_award.name })).to contain_exactly(issue1, issue2)
+ end
+ end
+
context 'when searching issues' do
it 'returns correct issues' do
expect(resolve_issues(search: 'foo')).to contain_exactly(issue2)
diff --git a/spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb b/spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb
index 23a496d85f8..f683ade978a 100644
--- a/spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb
@@ -85,7 +85,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
end
it 'passes database_replica_location' do
- expected_location = { main: location }
+ expected_location = { Gitlab::Database::MAIN_DATABASE_NAME.to_sym => location }
expect(load_balancer).to receive_message_chain(:host, "database_replica_location").and_return(location)
@@ -103,7 +103,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
end
it 'passes primary write location', :aggregate_failures do
- expected_location = { main: location }
+ expected_location = { Gitlab::Database::MAIN_DATABASE_NAME.to_sym => location }
expect(load_balancer).to receive(:primary_write_location).and_return(location)
@@ -116,28 +116,43 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
end
end
- shared_examples_for 'database location was already provided' do
- shared_examples_for 'does not set database location again' do |use_primary|
- before do
- allow(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_primary?).and_return(use_primary)
- end
+ context 'when worker cannot be constantized' do
+ let(:worker_class) { 'ActionMailer::MailDeliveryJob' }
+ let(:expected_consistency) { :always }
- it 'does not set database locations again' do
- run_middleware
+ include_examples 'does not pass database locations'
+ end
- expect(job['wal_locations']).to eq({ main: old_location })
- end
- end
+ context 'when worker class does not include ApplicationWorker' do
+ let(:worker_class) { ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper }
+ let(:expected_consistency) { :always }
+
+ include_examples 'does not pass database locations'
+ end
+ context 'database wal location was already provided' do
let(:old_location) { '0/D525E3A8' }
let(:new_location) { 'AB/12345' }
- let(:job) { { "job_id" => "a180b47c-3fd6-41b8-81e9-34da61c3400e", 'wal_locations' => { main: old_location } } }
+ let(:wal_locations) { { Gitlab::Database::MAIN_DATABASE_NAME.to_sym => old_location } }
+ let(:job) { { "job_id" => "a180b47c-3fd6-41b8-81e9-34da61c3400e", 'wal_locations' => wal_locations } }
before do
allow(load_balancer).to receive(:primary_write_location).and_return(new_location)
allow(load_balancer).to receive(:database_replica_location).and_return(new_location)
end
+ shared_examples_for 'does not set database location again' do |use_primary|
+ before do
+ allow(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_primary?).and_return(use_primary)
+ end
+
+ it 'does not set database locations again' do
+ run_middleware
+
+ expect(job['wal_locations']).to eq(wal_locations)
+ end
+ end
+
context "when write was performed" do
include_examples 'does not set database location again', true
end
@@ -147,24 +162,6 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
end
end
- context 'when worker cannot be constantized' do
- let(:worker_class) { 'ActionMailer::MailDeliveryJob' }
- let(:expected_consistency) { :always }
-
- include_examples 'does not pass database locations'
- end
-
- context 'when worker class does not include ApplicationWorker' do
- let(:worker_class) { ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper }
- let(:expected_consistency) { :always }
-
- include_examples 'does not pass database locations'
- end
-
- context 'database wal location was already provided' do
- include_examples 'database location was already provided'
- end
-
context 'when worker data consistency is :always' do
include_context 'data consistency worker class', :always, :load_balancing_for_test_data_consistency_worker
diff --git a/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb b/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
index fcb1d52fbaf..1fe348bbf2e 100644
--- a/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
@@ -63,10 +63,11 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
end
shared_examples_for 'replica is up to date' do |expected_strategy|
- let(:wal_locations) { { main: '0/D525E3A8' } }
+ let(:location) {'0/D525E3A8' }
+ let(:wal_locations) { { Gitlab::Database::MAIN_DATABASE_NAME.to_sym => location } }
it 'does not stick to the primary', :aggregate_failures do
- expect(load_balancer).to receive(:select_up_to_date_host).with(wal_locations[:main]).and_return(true)
+ expect(load_balancer).to receive(:select_up_to_date_host).with(location).and_return(true)
run_middleware do
expect(Gitlab::Database::LoadBalancing::Session.current.use_primary?).not_to be_truthy
@@ -91,7 +92,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware do
let(:job) { { 'job_id' => 'a180b47c-3fd6-41b8-81e9-34da61c3400e', 'wal_locations' => wal_locations } }
before do
- allow(load_balancer).to receive(:select_up_to_date_host).with(wal_locations[:main]).and_return(true)
+ allow(load_balancer).to receive(:select_up_to_date_host).with(location).and_return(true)
end
it_behaves_like 'replica is up to date', 'replica'
diff --git a/spec/lib/gitlab/database_importers/work_items/base_type_importer_spec.rb b/spec/lib/gitlab/database_importers/work_items/base_type_importer_spec.rb
new file mode 100644
index 00000000000..8c3d372cc55
--- /dev/null
+++ b/spec/lib/gitlab/database_importers/work_items/base_type_importer_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter do
+ subject { described_class.import }
+
+ it_behaves_like 'work item base types importer'
+end
diff --git a/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb b/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb
new file mode 100644
index 00000000000..ac2695977c4
--- /dev/null
+++ b/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Pagination::CursorBasedKeyset do
+ subject { described_class }
+
+ describe '.available_for_type?' do
+ it 'returns true for Group' do
+ expect(subject.available_for_type?(Group.all)).to be_truthy
+ end
+
+ it 'return false for other types of relations' do
+ expect(subject.available_for_type?(User.all)).to be_falsey
+ end
+ end
+
+ describe '.available?' do
+ let(:request_context) { double('request_context', params: { order_by: order_by, sort: sort }) }
+ let(:cursor_based_request_context) { Gitlab::Pagination::Keyset::CursorBasedRequestContext.new(request_context) }
+
+ context 'with order-by name asc' do
+ let(:order_by) { :name }
+ let(:sort) { :asc }
+
+ it 'returns true for Group' do
+ expect(subject.available?(cursor_based_request_context, Group.all)).to be_truthy
+ end
+
+ it 'return false for other types of relations' do
+ expect(subject.available?(cursor_based_request_context, User.all)).to be_falsey
+ end
+ end
+
+ context 'with other order-by columns' do
+ let(:order_by) { :path }
+ let(:sort) { :asc }
+
+ it 'returns false for Group' do
+ expect(subject.available?(cursor_based_request_context, Group.all)).to be_falsey
+ end
+
+ it 'return false for other types of relations' do
+ expect(subject.available?(cursor_based_request_context, User.all)).to be_falsey
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/pagination/keyset/cursor_based_request_context_spec.rb b/spec/lib/gitlab/pagination/keyset/cursor_based_request_context_spec.rb
index 4998abd2186..79de6f230ec 100644
--- a/spec/lib/gitlab/pagination/keyset/cursor_based_request_context_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/cursor_based_request_context_spec.rb
@@ -3,32 +3,40 @@
require 'spec_helper'
RSpec.describe Gitlab::Pagination::Keyset::CursorBasedRequestContext do
- let(:params) { { per_page: 2, cursor: 'eyJuYW1lIjoiR2l0TGFiIEluc3RhbmNlIiwiaWQiOiI1MiIsIl9rZCI6Im4ifQ==' } }
- let(:request) { double('request', params: params) }
+ let(:params) { { per_page: 2, cursor: 'eyJuYW1lIjoiR2l0TGFiIEluc3RhbmNlIiwiaWQiOiI1MiIsIl9rZCI6Im4ifQ==', order_by: :name, sort: :asc } }
+ let(:request) { double('request', url: 'http://localhost') }
+ let(:request_context) { double('request_context', header: nil, params: params, request: request) }
describe '#per_page' do
- subject(:per_page) { described_class.new(request).per_page }
+ subject(:per_page) { described_class.new(request_context).per_page }
it { is_expected.to eq 2 }
end
describe '#cursor' do
- subject(:cursor) { described_class.new(request).cursor }
+ subject(:cursor) { described_class.new(request_context).cursor }
it { is_expected.to eq 'eyJuYW1lIjoiR2l0TGFiIEluc3RhbmNlIiwiaWQiOiI1MiIsIl9rZCI6Im4ifQ==' }
end
+ describe '#order_by' do
+ subject(:order_by) { described_class.new(request_context).order_by }
+
+ it { is_expected.to eq({ name: :asc }) }
+ end
+
describe '#apply_headers' do
- let(:request) { double('request', url: "http://#{Gitlab.config.gitlab.host}/api/v4/projects?per_page=3", params: params) }
+ let(:request) { double('request', url: "http://#{Gitlab.config.gitlab.host}/api/v4/projects?per_page=3") }
let(:params) { { per_page: 3 } }
+ let(:request_context) { double('request_context', header: nil, params: params, request: request) }
let(:cursor_for_next_page) { 'eyJuYW1lIjoiSDVicCIsImlkIjoiMjgiLCJfa2QiOiJuIn0=' }
- subject(:apply_headers) { described_class.new(request).apply_headers(cursor_for_next_page) }
+ subject(:apply_headers) { described_class.new(request_context).apply_headers(cursor_for_next_page) }
it 'sets Link header with same host/path as the original request' do
- orig_uri = URI.parse(request.url)
+ orig_uri = URI.parse(request_context.request.url)
- expect(request).to receive(:header).once do |name, header|
+ expect(request_context).to receive(:header).once do |name, header|
first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures
uri = URI.parse(first_link)
@@ -42,9 +50,9 @@ RSpec.describe Gitlab::Pagination::Keyset::CursorBasedRequestContext do
end
it 'sets Link header with a cursor to the next page' do
- orig_uri = URI.parse(request.url)
+ orig_uri = URI.parse(request_context.request.url)
- expect(request).to receive(:header).once do |name, header|
+ expect(request_context).to receive(:header).once do |name, header|
first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures
query = CGI.parse(URI.parse(first_link).query)
diff --git a/spec/lib/gitlab/pagination/keyset/cursor_pager_spec.rb b/spec/lib/gitlab/pagination/keyset/cursor_pager_spec.rb
index 5ba381834ea..783e728b34c 100644
--- a/spec/lib/gitlab/pagination/keyset/cursor_pager_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/cursor_pager_spec.rb
@@ -6,8 +6,8 @@ RSpec.describe Gitlab::Pagination::Keyset::CursorPager do
let(:relation) { Group.all.order(:name, :id) }
let(:per_page) { 3 }
let(:params) { { cursor: nil, per_page: per_page } }
- let(:request) { double('request', params: params) }
- let(:cursor_based_request_context) { Gitlab::Pagination::Keyset::CursorBasedRequestContext.new(request) }
+ let(:request_context) { double('request_context', params: params) }
+ let(:cursor_based_request_context) { Gitlab::Pagination::Keyset::CursorBasedRequestContext.new(request_context) }
before_all do
create_list(:group, 7)
@@ -33,7 +33,7 @@ RSpec.describe Gitlab::Pagination::Keyset::CursorPager do
it 'passes information about next page to request' do
cursor_for_next_page = relation.keyset_paginate(**params).cursor_for_next_page
- expect_next_instance_of(Gitlab::Pagination::Keyset::HeaderBuilder, cursor_based_request_context) do |builder|
+ expect_next_instance_of(Gitlab::Pagination::Keyset::HeaderBuilder, request_context) do |builder|
expect(builder).to receive(:add_next_page_header).with({ cursor: cursor_for_next_page })
end
diff --git a/spec/migrations/20210831203408_upsert_base_work_item_types_spec.rb b/spec/migrations/20210831203408_upsert_base_work_item_types_spec.rb
new file mode 100644
index 00000000000..b9cc9de88cc
--- /dev/null
+++ b/spec/migrations/20210831203408_upsert_base_work_item_types_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!('upsert_base_work_item_types')
+
+RSpec.describe UpsertBaseWorkItemTypes, :migration do
+ let!(:work_item_types) { table(:work_item_types) }
+
+ context 'when no default types exist' do
+ it 'creates default data' do
+ expect(work_item_types.count).to eq(0)
+
+ reversible_migration do |migration|
+ migration.before -> {
+ # Depending on whether the migration has been run before,
+ # the size could be 4, or 0, so we don't set any expectations
+ # as we don't delete base types on migration reverse
+ }
+
+ migration.after -> {
+ expect(work_item_types.count).to eq(4)
+ expect(work_item_types.all.pluck(:base_type)).to match_array(WorkItem::Type.base_types.values)
+ }
+ end
+ end
+ end
+
+ context 'when default types already exist' do
+ before do
+ Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.import
+ end
+
+ it 'does not create default types again' do
+ expect(work_item_types.all.pluck(:base_type)).to match_array(WorkItem::Type.base_types.values)
+
+ reversible_migration do |migration|
+ migration.before -> {
+ expect(work_item_types.all.pluck(:base_type)).to match_array(WorkItem::Type.base_types.values)
+ }
+
+ migration.after -> {
+ expect(work_item_types.count).to eq(4)
+ expect(work_item_types.all.pluck(:base_type)).to match_array(WorkItem::Type.base_types.values)
+ }
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/pipeline_variable_spec.rb b/spec/models/ci/pipeline_variable_spec.rb
index 04fcaab4c2d..4e8d49585d0 100644
--- a/spec/models/ci/pipeline_variable_spec.rb
+++ b/spec/models/ci/pipeline_variable_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Ci::PipelineVariable do
it_behaves_like "CI variable"
- it { is_expected.to validate_uniqueness_of(:key).scoped_to(:pipeline_id) }
+ it { is_expected.to validate_presence_of(:key) }
describe '#hook_attrs' do
let(:variable) { create(:ci_pipeline_variable, key: 'foo', value: 'bar') }
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index ff0d7ecceb5..c6b4d82bf15 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -61,6 +61,34 @@ RSpec.describe 'getting an issue list for a project' do
end
end
+ context 'filtering by my_reaction_emoji' do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:upvote_award) { create(:award_emoji, :upvote, user: current_user, awardable: issue_a) }
+
+ let(:issue_a_gid) { issue_a.to_global_id.to_s }
+ let(:issue_b_gid) { issue_b.to_global_id.to_s }
+
+ where(:value, :gids) do
+ 'thumbsup' | lazy { [issue_a_gid] }
+ 'ANY' | lazy { [issue_a_gid] }
+ 'any' | lazy { [issue_a_gid] }
+ 'AnY' | lazy { [issue_a_gid] }
+ 'NONE' | lazy { [issue_b_gid] }
+ 'thumbsdown' | lazy { [] }
+ end
+
+ with_them do
+ let(:issue_filter_params) { { my_reaction_emoji: value } }
+
+ it 'returns correctly filtered issues' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_dig_at(issues_data, :node, :id)).to eq(gids)
+ end
+ end
+ end
+
context 'when limiting the number of results' do
let(:query) do
<<~GQL
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 30df47ccc41..38abedde7da 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -158,6 +158,127 @@ RSpec.describe API::Groups do
end
end
+ context 'pagination strategies' do
+ let_it_be(:group_1) { create(:group, name: '1_group') }
+ let_it_be(:group_2) { create(:group, name: '2_group') }
+
+ context 'when the user is anonymous' do
+ context 'offset pagination' do
+ context 'on making requests beyond the allowed offset pagination threshold' do
+ it 'returns error and suggests to use keyset pagination' do
+ get api('/groups'), params: { page: 3000, per_page: 25 }
+
+ expect(response).to have_gitlab_http_status(:method_not_allowed)
+ expect(json_response['error']).to eq(
+ 'Offset pagination has a maximum allowed offset of 50000 for requests that return objects of type Group. '\
+ 'Remaining records can be retrieved using keyset pagination.'
+ )
+ end
+
+ context 'when the feature flag `keyset_pagination_for_groups_api` is disabled' do
+ before do
+ stub_feature_flags(keyset_pagination_for_groups_api: false)
+ end
+
+ it 'returns successful response' do
+ get api('/groups'), params: { page: 3000, per_page: 25 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'on making requests below the allowed offset pagination threshold' do
+ it 'paginates the records' do
+ get api('/groups'), params: { page: 1, per_page: 1 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ records = json_response
+ expect(records.size).to eq(1)
+ expect(records.first['id']).to eq(group_1.id)
+
+ # next page
+
+ get api('/groups'), params: { page: 2, per_page: 1 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ records = Gitlab::Json.parse(response.body)
+ expect(records.size).to eq(1)
+ expect(records.first['id']).to eq(group_2.id)
+ end
+ end
+ end
+
+ context 'keyset pagination' do
+ def pagination_links(response)
+ link = response.headers['LINK']
+ return unless link
+
+ link.split(',').map do |link|
+ match = link.match(/<(?<url>.*)>; rel="(?<rel>\w+)"/)
+ break nil unless match
+
+ { url: match[:url], rel: match[:rel] }
+ end.compact
+ end
+
+ def params_for_next_page(response)
+ next_url = pagination_links(response).find { |link| link[:rel] == 'next' }[:url]
+ Rack::Utils.parse_query(URI.parse(next_url).query)
+ end
+
+ context 'on making requests with supported ordering structure' do
+ it 'paginates the records correctly' do
+ # first page
+ get api('/groups'), params: { pagination: 'keyset', per_page: 1 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ records = json_response
+ expect(records.size).to eq(1)
+ expect(records.first['id']).to eq(group_1.id)
+
+ params_for_next_page = params_for_next_page(response)
+ expect(params_for_next_page).to include('cursor')
+
+ get api('/groups'), params: params_for_next_page
+
+ expect(response).to have_gitlab_http_status(:ok)
+ records = Gitlab::Json.parse(response.body)
+ expect(records.size).to eq(1)
+ expect(records.first['id']).to eq(group_2.id)
+ end
+
+ context 'when the feature flag `keyset_pagination_for_groups_api` is disabled' do
+ before do
+ stub_feature_flags(keyset_pagination_for_groups_api: false)
+ end
+
+ it 'ignores the keyset pagination params and performs offset pagination' do
+ get api('/groups'), params: { pagination: 'keyset', per_page: 1 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ records = json_response
+ expect(records.size).to eq(1)
+ expect(records.first['id']).to eq(group_1.id)
+
+ params_for_next_page = params_for_next_page(response)
+ expect(params_for_next_page).not_to include('cursor')
+ end
+ end
+ end
+
+ context 'on making requests with unsupported ordering structure' do
+ it 'returns error' do
+ get api('/groups'), params: { pagination: 'keyset', per_page: 1, order_by: 'path', sort: 'desc' }
+
+ expect(response).to have_gitlab_http_status(:method_not_allowed)
+ expect(json_response['error']).to eq('Keyset pagination is not yet available for this type of request')
+ end
+ end
+ end
+ end
+ end
+
context "when authenticated as admin" do
it "admin: returns an array of all groups" do
get api("/groups", admin)
diff --git a/spec/support/shared_examples/work_item_base_types_importer.rb b/spec/support/shared_examples/work_item_base_types_importer.rb
new file mode 100644
index 00000000000..2c02b76d49b
--- /dev/null
+++ b/spec/support/shared_examples/work_item_base_types_importer.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'work item base types importer' do
+ it 'creates all base work item types' do
+ expect { subject }.to change(WorkItem::Type, :count).from(0).to(WorkItem::Type::BASE_TYPES.count)
+ end
+end