diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-02 03:09:56 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-02 03:09:56 +0300 |
commit | 926711e4546e0cf845c6cbe5773076f2195357f0 (patch) | |
tree | 92edf881d2be503589848c218a85d2a584cf0d19 /spec | |
parent | f7bc7dc5eafc4eef9043a3d1b2dcbc15ca76a571 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
26 files changed, 387 insertions, 82 deletions
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 0235d7eb95a..218aa04dd3f 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -704,7 +704,7 @@ RSpec.describe ApplicationController do get :index - expect(response.headers['Cache-Control']).to eq 'max-age=0, private, must-revalidate, no-store' + expect(response.headers['Cache-Control']).to eq 'no-store' expect(response.headers['Pragma']).to eq 'no-cache' end @@ -740,7 +740,7 @@ RSpec.describe ApplicationController do it 'sets no-cache headers', :aggregate_failures do subject - expect(response.headers['Cache-Control']).to eq 'no-cache, no-store' + expect(response.headers['Cache-Control']).to eq 'no-store' expect(response.headers['Pragma']).to eq 'no-cache' expect(response.headers['Expires']).to eq 'Fri, 01 Jan 1990 00:00:00 GMT' end diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 32ac83847aa..3a2986f6cbe 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -258,7 +258,7 @@ RSpec.describe SearchController do expect(response).to have_gitlab_http_status(:ok) - expect(response.headers['Cache-Control']).to include('max-age=60, private') + expect(response.headers['Cache-Control']).to eq('no-store') end end diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index e0e2a012c81..325d675a68c 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -269,7 +269,7 @@ RSpec.describe 'Database schema' do sql = <<~SQL SELECT table_name, column_name, data_type FROM information_schema.columns - WHERE table_catalog = '#{ApplicationRecord.connection_config[:database]}' + WHERE table_catalog = '#{ApplicationRecord.connection_db_config.database}' AND table_schema = 'public' AND table_name NOT LIKE 'pg_%' AND data_type = 'jsonb' diff --git a/spec/features/projects/badges/pipeline_badge_spec.rb b/spec/features/projects/badges/pipeline_badge_spec.rb index bfc924b5d9b..9d8f9872a1a 100644 --- a/spec/features/projects/badges/pipeline_badge_spec.rb +++ b/spec/features/projects/badges/pipeline_badge_spec.rb @@ -68,7 +68,7 @@ RSpec.describe 'Pipeline Badge' do visit pipeline_project_badges_path(project, ref: ref, format: :svg) expect(page.status_code).to eq(200) - expect(page.response_headers['Cache-Control']).to include 'no-cache' + expect(page.response_headers['Cache-Control']).to eq('no-store') end end diff --git a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js index aa1752d187f..61a8f821b30 100644 --- a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js +++ b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js @@ -118,6 +118,7 @@ describe('RunnerList', () => { { filters: mockFilters, sort: mockDefaultSort, + pagination: { page: 1 }, }, ]); }); @@ -129,6 +130,7 @@ describe('RunnerList', () => { { filters: [], sort: mockOtherSort, + pagination: { page: 1 }, }, ]); }); diff --git a/spec/frontend/runner/components/runner_pagination_spec.js b/spec/frontend/runner/components/runner_pagination_spec.js new file mode 100644 index 00000000000..59feb32dd2a --- /dev/null +++ b/spec/frontend/runner/components/runner_pagination_spec.js @@ -0,0 +1,160 @@ +import { GlPagination } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import RunnerPagination from '~/runner/components/runner_pagination.vue'; + +const mockStartCursor = 'START_CURSOR'; +const mockEndCursor = 'END_CURSOR'; + +describe('RunnerPagination', () => { + let wrapper; + + const findPagination = () => wrapper.findComponent(GlPagination); + + const createComponent = ({ page = 1, hasPreviousPage = false, hasNextPage = true } = {}) => { + wrapper = mount(RunnerPagination, { + propsData: { + value: { + page, + }, + pageInfo: { + hasPreviousPage, + hasNextPage, + startCursor: mockStartCursor, + endCursor: mockEndCursor, + }, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('When on the first page', () => { + beforeEach(() => { + createComponent({ + page: 1, + hasPreviousPage: false, + hasNextPage: true, + }); + }); + + it('Contains the current page information', () => { + expect(findPagination().props('value')).toBe(1); + expect(findPagination().props('prevPage')).toBe(null); + expect(findPagination().props('nextPage')).toBe(2); + }); + + it('Shows prev page disabled', () => { + expect(findPagination().find('[aria-disabled]').text()).toBe('Prev'); + }); + + it('Shows next page link', () => { + expect(findPagination().find('a').text()).toBe('Next'); + }); + + it('Goes to the second page', () => { + findPagination().vm.$emit('input', 2); + + expect(wrapper.emitted('input')[0]).toEqual([ + { + after: mockEndCursor, + page: 2, + }, + ]); + }); + }); + + describe('When in between pages', () => { + beforeEach(() => { + createComponent({ + page: 2, + hasPreviousPage: true, + hasNextPage: true, + }); + }); + + it('Contains the current page information', () => { + expect(findPagination().props('value')).toBe(2); + expect(findPagination().props('prevPage')).toBe(1); + expect(findPagination().props('nextPage')).toBe(3); + }); + + it('Shows the next and previous pages', () => { + const links = findPagination().findAll('a'); + + expect(links).toHaveLength(2); + expect(links.at(0).text()).toBe('Prev'); + expect(links.at(1).text()).toBe('Next'); + }); + + it('Goes to the last page', () => { + findPagination().vm.$emit('input', 3); + + expect(wrapper.emitted('input')[0]).toEqual([ + { + after: mockEndCursor, + page: 3, + }, + ]); + }); + + it('Goes to the first page', () => { + findPagination().vm.$emit('input', 1); + + expect(wrapper.emitted('input')[0]).toEqual([ + { + before: mockStartCursor, + page: 1, + }, + ]); + }); + }); + + describe('When in the last page', () => { + beforeEach(() => { + createComponent({ + page: 3, + hasPreviousPage: true, + hasNextPage: false, + }); + }); + + it('Contains the current page', () => { + expect(findPagination().props('value')).toBe(3); + expect(findPagination().props('prevPage')).toBe(2); + expect(findPagination().props('nextPage')).toBe(null); + }); + + it('Shows next page link', () => { + expect(findPagination().find('a').text()).toBe('Prev'); + }); + + it('Shows next page disabled', () => { + expect(findPagination().find('[aria-disabled]').text()).toBe('Next'); + }); + }); + + describe('When only one page', () => { + beforeEach(() => { + createComponent({ + page: 1, + hasPreviousPage: false, + hasNextPage: false, + }); + }); + + it('does not display pagination', () => { + expect(wrapper.html()).toBe(''); + }); + + it('Contains the current page', () => { + expect(findPagination().props('value')).toBe(1); + }); + + it('Shows no more page buttons', () => { + expect(findPagination().props('prevPage')).toBe(null); + expect(findPagination().props('nextPage')).toBe(null); + }); + }); +}); diff --git a/spec/frontend/runner/mock_data.js b/spec/frontend/runner/mock_data.js index 3c1b3a0b5e2..744942bfa73 100644 --- a/spec/frontend/runner/mock_data.js +++ b/spec/frontend/runner/mock_data.js @@ -31,6 +31,13 @@ export const runnersData = { __typename: 'CiRunner', }, ], + pageInfo: { + endCursor: 'GRAPHQL_END_CURSOR', + startCursor: 'GRAPHQL_START_CURSOR', + hasNextPage: true, + hasPreviousPage: false, + __typename: 'PageInfo', + }, __typename: 'CiRunnerConnection', }, }, diff --git a/spec/frontend/runner/runner_list/filtered_search_utils_spec.js b/spec/frontend/runner/runner_list/filtered_search_utils_spec.js index e46821d6504..abbe05452d1 100644 --- a/spec/frontend/runner/runner_list/filtered_search_utils_spec.js +++ b/spec/frontend/runner/runner_list/filtered_search_utils_spec.js @@ -1,3 +1,4 @@ +import { RUNNER_PAGE_SIZE } from '~/runner/constants'; import { fromUrlQueryToSearch, fromSearchToUrl, @@ -9,26 +10,28 @@ describe('search_params.js', () => { { name: 'a default query', urlQuery: '', - search: { filters: [], sort: 'CREATED_DESC' }, - graphqlVariables: { sort: 'CREATED_DESC' }, + search: { filters: [], pagination: { page: 1 }, sort: 'CREATED_DESC' }, + graphqlVariables: { sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE }, }, { name: 'a single status', urlQuery: '?status[]=ACTIVE', search: { filters: [{ type: 'status', value: { data: 'ACTIVE', operator: '=' } }], + pagination: { page: 1 }, sort: 'CREATED_DESC', }, - graphqlVariables: { status: 'ACTIVE', sort: 'CREATED_DESC' }, + graphqlVariables: { status: 'ACTIVE', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE }, }, { name: 'single instance type', urlQuery: '?runner_type[]=INSTANCE_TYPE', search: { filters: [{ type: 'runner_type', value: { data: 'INSTANCE_TYPE', operator: '=' } }], + pagination: { page: 1 }, sort: 'CREATED_DESC', }, - graphqlVariables: { type: 'INSTANCE_TYPE', sort: 'CREATED_DESC' }, + graphqlVariables: { type: 'INSTANCE_TYPE', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE }, }, { name: 'multiple runner status', @@ -38,9 +41,10 @@ describe('search_params.js', () => { { type: 'status', value: { data: 'ACTIVE', operator: '=' } }, { type: 'status', value: { data: 'PAUSED', operator: '=' } }, ], + pagination: { page: 1 }, sort: 'CREATED_DESC', }, - graphqlVariables: { status: 'ACTIVE', sort: 'CREATED_DESC' }, + graphqlVariables: { status: 'ACTIVE', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE }, }, { name: 'multiple status, a single instance type and a non default sort', @@ -50,9 +54,52 @@ describe('search_params.js', () => { { type: 'status', value: { data: 'ACTIVE', operator: '=' } }, { type: 'runner_type', value: { data: 'INSTANCE_TYPE', operator: '=' } }, ], + pagination: { page: 1 }, sort: 'CREATED_ASC', }, - graphqlVariables: { status: 'ACTIVE', type: 'INSTANCE_TYPE', sort: 'CREATED_ASC' }, + graphqlVariables: { + status: 'ACTIVE', + type: 'INSTANCE_TYPE', + sort: 'CREATED_ASC', + first: RUNNER_PAGE_SIZE, + }, + }, + { + name: 'the next page', + urlQuery: '?page=2&after=AFTER_CURSOR', + search: { filters: [], pagination: { page: 2, after: 'AFTER_CURSOR' }, sort: 'CREATED_DESC' }, + graphqlVariables: { sort: 'CREATED_DESC', after: 'AFTER_CURSOR', first: RUNNER_PAGE_SIZE }, + }, + { + name: 'the previous page', + urlQuery: '?page=2&before=BEFORE_CURSOR', + search: { + filters: [], + pagination: { page: 2, before: 'BEFORE_CURSOR' }, + sort: 'CREATED_DESC', + }, + graphqlVariables: { sort: 'CREATED_DESC', before: 'BEFORE_CURSOR', last: RUNNER_PAGE_SIZE }, + }, + { + name: + 'the next page filtered by multiple status, a single instance type and a non default sort', + urlQuery: + '?status[]=ACTIVE&runner_type[]=INSTANCE_TYPE&sort=CREATED_ASC&page=2&after=AFTER_CURSOR', + search: { + filters: [ + { type: 'status', value: { data: 'ACTIVE', operator: '=' } }, + { type: 'runner_type', value: { data: 'INSTANCE_TYPE', operator: '=' } }, + ], + pagination: { page: 2, after: 'AFTER_CURSOR' }, + sort: 'CREATED_ASC', + }, + graphqlVariables: { + status: 'ACTIVE', + type: 'INSTANCE_TYPE', + sort: 'CREATED_ASC', + after: 'AFTER_CURSOR', + first: RUNNER_PAGE_SIZE, + }, }, ]; @@ -62,6 +109,24 @@ describe('search_params.js', () => { expect(fromUrlQueryToSearch(urlQuery)).toEqual(search); }); }); + + it('When a page cannot be parsed as a number, it defaults to `1`', () => { + expect(fromUrlQueryToSearch('?page=NONSENSE&after=AFTER_CURSOR').pagination).toEqual({ + page: 1, + }); + }); + + it('When a page is less than 1, it defaults to `1`', () => { + expect(fromUrlQueryToSearch('?page=0&after=AFTER_CURSOR').pagination).toEqual({ + page: 1, + }); + }); + + it('When a page with no cursor is given, it defaults to `1`', () => { + expect(fromUrlQueryToSearch('?page=2').pagination).toEqual({ + page: 1, + }); + }); }); describe('fromSearchToUrl', () => { diff --git a/spec/frontend/runner/runner_list/runner_list_app_spec.js b/spec/frontend/runner/runner_list/runner_list_app_spec.js index 19a5a60d2c1..e908e62db4f 100644 --- a/spec/frontend/runner/runner_list/runner_list_app_spec.js +++ b/spec/frontend/runner/runner_list/runner_list_app_spec.js @@ -1,5 +1,5 @@ import * as Sentry from '@sentry/browser'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { createLocalVue, mount, shallowMount } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import { TEST_HOST } from 'helpers/test_constants'; @@ -9,14 +9,17 @@ import { updateHistory } from '~/lib/utils/url_utility'; import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue'; import RunnerList from '~/runner/components/runner_list.vue'; import RunnerManualSetupHelp from '~/runner/components/runner_manual_setup_help.vue'; +import RunnerPagination from '~/runner/components/runner_pagination.vue'; import RunnerTypeHelp from '~/runner/components/runner_type_help.vue'; import { CREATED_ASC, + CREATED_DESC, DEFAULT_SORT, INSTANCE_TYPE, PARAM_KEY_STATUS, STATUS_ACTIVE, + RUNNER_PAGE_SIZE, } from '~/runner/constants'; import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql'; import RunnerListApp from '~/runner/runner_list/runner_list_app.vue'; @@ -26,6 +29,7 @@ import { runnersData } from '../mock_data'; const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN'; const mockActiveRunnersCount = 2; const mocKRunners = runnersData.data.runners.nodes; +const mockPageInfo = runnersData.data.runners.pageInfo; jest.mock('@sentry/browser'); jest.mock('~/lib/utils/url_utility', () => ({ @@ -44,6 +48,7 @@ describe('RunnerListApp', () => { const findRunnerTypeHelp = () => wrapper.findComponent(RunnerTypeHelp); const findRunnerManualSetupHelp = () => wrapper.findComponent(RunnerManualSetupHelp); const findRunnerList = () => wrapper.findComponent(RunnerList); + const findRunnerPagination = () => wrapper.findComponent(RunnerPagination); const findRunnerFilteredSearchBar = () => wrapper.findComponent(RunnerFilteredSearchBar); const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => { @@ -101,6 +106,7 @@ describe('RunnerListApp', () => { status: undefined, type: undefined, sort: DEFAULT_SORT, + first: RUNNER_PAGE_SIZE, }); }); @@ -128,6 +134,7 @@ describe('RunnerListApp', () => { { type: 'runner_type', value: { data: INSTANCE_TYPE, operator: '=' } }, ], sort: 'CREATED_DESC', + pagination: { page: 1 }, }); }); @@ -136,6 +143,7 @@ describe('RunnerListApp', () => { status: STATUS_ACTIVE, type: INSTANCE_TYPE, sort: DEFAULT_SORT, + first: RUNNER_PAGE_SIZE, }); }); }); @@ -159,6 +167,7 @@ describe('RunnerListApp', () => { expect(mockRunnersQuery).toHaveBeenLastCalledWith({ status: STATUS_ACTIVE, sort: CREATED_ASC, + first: RUNNER_PAGE_SIZE, }); }); }); @@ -193,4 +202,37 @@ describe('RunnerListApp', () => { expect(Sentry.captureException).toHaveBeenCalled(); }); }); + + describe('Pagination', () => { + beforeEach(() => { + createComponentWithApollo({ mountFn: mount }); + }); + + it('more pages can be selected', () => { + expect(findRunnerPagination().text()).toMatchInterpolatedText('Prev Next'); + }); + + it('cannot navigate to the previous page', () => { + expect(findRunnerPagination().find('[aria-disabled]').text()).toBe('Prev'); + }); + + it('navigates to the next page', async () => { + const nextPageBtn = findRunnerPagination().find('a'); + expect(nextPageBtn.text()).toBe('Next'); + + await nextPageBtn.trigger('click'); + + expect(mockRunnersQuery).toHaveBeenLastCalledWith({ + sort: CREATED_DESC, + first: RUNNER_PAGE_SIZE, + after: expect.any(String), + }); + + expect(mockRunnersQuery).toHaveBeenLastCalledWith({ + sort: CREATED_DESC, + first: RUNNER_PAGE_SIZE, + after: mockPageInfo.endCursor, + }); + }); + }); }); diff --git a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb index f878d24fe4b..63625244fe8 100644 --- a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb +++ b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do let(:chunked_io) { described_class.new(build) } before do - stub_feature_flags(ci_enable_live_trace: true, gitlab_ci_trace_read_consistency: true) + stub_feature_flags(ci_enable_live_trace: true) end describe "#initialize" do diff --git a/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb b/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb index 59f70165380..28d78c182ad 100644 --- a/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb +++ b/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb @@ -3,8 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do - let(:pool_spec) { ActiveRecord::Base.connection_pool.spec } - let(:pool) { ActiveRecord::ConnectionAdapters::ConnectionPool.new(pool_spec) } + let(:pool) { Gitlab::Database.create_connection_pool(2) } let(:conflict_error) { Class.new(RuntimeError) } let(:lb) { described_class.new(%w(localhost localhost)) } diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb index 79ddb450d7a..4f1d6302331 100644 --- a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb +++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb @@ -580,7 +580,7 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe it 'idempotently cleans up after failed background migrations' do expect(partitioned_model.count).to eq(0) - partitioned_model.insert!(record2.attributes) + partitioned_model.insert(record2.attributes, unique_by: [:id, :created_at]) expect_next_instance_of(Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable) do |backfill| allow(backfill).to receive(:transaction_open?).and_return(false) diff --git a/spec/lib/gitlab/database/with_lock_retries_spec.rb b/spec/lib/gitlab/database/with_lock_retries_spec.rb index b08f39fc92a..df2c506e163 100644 --- a/spec/lib/gitlab/database/with_lock_retries_spec.rb +++ b/spec/lib/gitlab/database/with_lock_retries_spec.rb @@ -242,10 +242,10 @@ RSpec.describe Gitlab::Database::WithLockRetries do let(:timing_configuration) { [[0.015.seconds, 0.025.seconds], [0.015.seconds, 0.025.seconds]] } # 15ms, 25ms it 'executes `SET LOCAL lock_timeout` using the configured timeout value in milliseconds' do - expect(ActiveRecord::Base.connection).to receive(:execute).with("SAVEPOINT active_record_1").and_call_original - expect(ActiveRecord::Base.connection).to receive(:execute).with('RESET idle_in_transaction_session_timeout; RESET lock_timeout').and_call_original + expect(ActiveRecord::Base.connection).to receive(:execute).with("RESET idle_in_transaction_session_timeout; RESET lock_timeout").and_call_original + expect(ActiveRecord::Base.connection).to receive(:execute).with("SAVEPOINT active_record_1", "TRANSACTION").and_call_original expect(ActiveRecord::Base.connection).to receive(:execute).with("SET LOCAL lock_timeout TO '15ms'").and_call_original - expect(ActiveRecord::Base.connection).to receive(:execute).with("RELEASE SAVEPOINT active_record_1").and_call_original + expect(ActiveRecord::Base.connection).to receive(:execute).with("RELEASE SAVEPOINT active_record_1", "TRANSACTION").and_call_original subject.run { } end diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 2b31f3b4dee..5c9af1206c0 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -329,7 +329,7 @@ RSpec.describe Gitlab::Database do expect(pool) .to be_kind_of(ActiveRecord::ConnectionAdapters::ConnectionPool) - expect(pool.spec.config[:pool]).to eq(5) + expect(pool.db_config.pool).to eq(5) ensure pool.disconnect! end @@ -339,7 +339,7 @@ RSpec.describe Gitlab::Database do pool = described_class.create_connection_pool(5, '127.0.0.1') begin - expect(pool.spec.config[:host]).to eq('127.0.0.1') + expect(pool.db_config.host).to eq('127.0.0.1') ensure pool.disconnect! end @@ -349,8 +349,8 @@ RSpec.describe Gitlab::Database do pool = described_class.create_connection_pool(5, '127.0.0.1', 5432) begin - expect(pool.spec.config[:host]).to eq('127.0.0.1') - expect(pool.spec.config[:port]).to eq(5432) + expect(pool.db_config.host).to eq('127.0.0.1') + expect(pool.db_config.configuration_hash[:port]).to eq(5432) ensure pool.disconnect! end diff --git a/spec/lib/gitlab/import_export/import_failure_service_spec.rb b/spec/lib/gitlab/import_export/import_failure_service_spec.rb index c8bb067d40c..51f1fc9c6a2 100644 --- a/spec/lib/gitlab/import_export/import_failure_service_spec.rb +++ b/spec/lib/gitlab/import_export/import_failure_service_spec.rb @@ -43,7 +43,7 @@ RSpec.describe Gitlab::ImportExport::ImportFailureService do let(:importable) { create(:merge_request) } it 'raise exception' do - expect { subject }.to raise_exception(ActiveRecord::AssociationNotFoundError, "Association named 'import_failures' was not found on MergeRequest; perhaps you misspelled it?") + expect { subject }.to raise_exception(ActiveRecord::AssociationNotFoundError, /Association named 'import_failures' was not found on MergeRequest/) end end end diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb index 12bc5d9aa3c..c15910ef529 100644 --- a/spec/models/ci/build_trace_chunk_spec.rb +++ b/spec/models/ci/build_trace_chunk_spec.rb @@ -18,7 +18,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do it_behaves_like 'having unique enum values' before do - stub_feature_flags(ci_enable_live_trace: true, gitlab_ci_trace_read_consistency: true) + stub_feature_flags(ci_enable_live_trace: true) stub_artifacts_object_storage end diff --git a/spec/models/clusters/clusters_hierarchy_spec.rb b/spec/models/clusters/clusters_hierarchy_spec.rb index 5ac561eb2d0..5dd2fe98352 100644 --- a/spec/models/clusters/clusters_hierarchy_spec.rb +++ b/spec/models/clusters/clusters_hierarchy_spec.rb @@ -4,8 +4,8 @@ require 'spec_helper' RSpec.describe Clusters::ClustersHierarchy do describe '#base_and_ancestors' do - def base_and_ancestors(clusterable, include_management_project: true) - described_class.new(clusterable, include_management_project: include_management_project).base_and_ancestors + def base_and_ancestors(clusterable) + described_class.new(clusterable).base_and_ancestors end context 'project in nested group with clusters at every level' do @@ -101,10 +101,6 @@ RSpec.describe Clusters::ClustersHierarchy do expect(base_and_ancestors(management_project)).to eq([ancestor, child]) end - it 'returns clusters for management_project' do - expect(base_and_ancestors(management_project, include_management_project: false)).to eq([child, ancestor]) - end - it 'returns clusters for project' do expect(base_and_ancestors(project)).to eq([child, ancestor]) end diff --git a/spec/models/concerns/bulk_insert_safe_spec.rb b/spec/models/concerns/bulk_insert_safe_spec.rb index ca6df506ee8..209ee1264d5 100644 --- a/spec/models/concerns/bulk_insert_safe_spec.rb +++ b/spec/models/concerns/bulk_insert_safe_spec.rb @@ -20,6 +20,13 @@ RSpec.describe BulkInsertSafe do t.index :name, unique: true end + + create_table :bulk_insert_items_with_composite_pk, id: false, force: true do |t| + t.integer :id, null: true + t.string :name, null: true + end + + execute("ALTER TABLE bulk_insert_items_with_composite_pk ADD PRIMARY KEY (id,name);") end end @@ -27,6 +34,7 @@ RSpec.describe BulkInsertSafe do ActiveRecord::Schema.define do drop_table :bulk_insert_items, force: true drop_table :bulk_insert_parent_items, force: true + drop_table :bulk_insert_items_with_composite_pk, force: true end end @@ -227,5 +235,28 @@ RSpec.describe BulkInsertSafe do end end end + + context 'when a model with composite primary key is inserted' do + let_it_be(:bulk_insert_items_with_composite_pk_class) do + Class.new(ActiveRecord::Base) do + self.table_name = 'bulk_insert_items_with_composite_pk' + + include BulkInsertSafe + end + end + + let(:new_object) { bulk_insert_items_with_composite_pk_class.new(id: 1, name: 'composite') } + + it 'successfully inserts an item' do + expect(ActiveRecord::InsertAll).to receive(:new) + .with( + bulk_insert_items_with_composite_pk_class, [new_object.as_json], on_duplicate: :raise, returning: false, unique_by: %w[id name] + ).and_call_original + + expect { bulk_insert_items_with_composite_pk_class.bulk_insert!([new_object]) }.to( + change(bulk_insert_items_with_composite_pk_class, :count).from(0).to(1) + ) + end + end end end diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb index 2bb6aa27e21..7fa55184cf1 100644 --- a/spec/models/concerns/deployment_platform_spec.rb +++ b/spec/models/concerns/deployment_platform_spec.rb @@ -254,20 +254,8 @@ RSpec.describe DeploymentPlatform do create(:cluster, :provided_by_user, projects: [another_project], management_project: project) end - context 'cluster_management_project feature is enabled' do - it 'returns the cluster with management project' do - is_expected.to eq(cluster_with_management_project.platform_kubernetes) - end - end - - context 'cluster_management_project feature is disabled' do - before do - stub_feature_flags(cluster_management_project: false) - end - - it 'returns nothing' do - is_expected.to be_nil - end + it 'returns the cluster with management project' do + is_expected.to eq(cluster_with_management_project.platform_kubernetes) end end @@ -311,20 +299,8 @@ RSpec.describe DeploymentPlatform do create(:cluster, :provided_by_user, projects: [another_project], management_project: project) end - context 'cluster_management_project feature is enabled' do - it 'returns the cluster with management project' do - is_expected.to eq(cluster_with_management_project.platform_kubernetes) - end - end - - context 'cluster_management_project feature is disabled' do - before do - stub_feature_flags(cluster_management_project: false) - end - - it 'returns the group cluster' do - is_expected.to eq(group_cluster.platform_kubernetes) - end + it 'returns the cluster with management project' do + is_expected.to eq(cluster_with_management_project.platform_kubernetes) end end diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 71a4a1a2784..869df06b60c 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -558,8 +558,7 @@ RSpec.describe API::Files do get api(url, current_user), params: params - expect(response.headers["Cache-Control"]).to include("no-store") - expect(response.headers["Cache-Control"]).to include("no-cache") + expect(response.headers["Cache-Control"]).to eq("max-age=0, private, must-revalidate, no-store, no-cache") expect(response.headers["Pragma"]).to eq("no-cache") expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT") end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 16619017dfe..e103aa3d6de 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -3864,6 +3864,48 @@ RSpec.describe API::Projects do end end + describe 'GET /projects/:id/storage' do + context 'when unauthenticated' do + it 'does not return project storage data' do + get api("/projects/#{project.id}/storage") + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + + it 'returns project storage data when user is admin' do + get api("/projects/#{project.id}/storage", create(:admin)) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['project_id']).to eq(project.id) + expect(json_response['disk_path']).to eq(project.repository.disk_path) + expect(json_response['created_at']).to be_present + expect(json_response['repository_storage']).to eq(project.repository_storage) + end + + it 'does not return project storage data when user is not admin' do + get api("/projects/#{project.id}/storage", user3) + + expect(response).to have_gitlab_http_status(:forbidden) + end + + it 'responds with a 401 for unauthenticated users trying to access a non-existent project id' do + expect(Project.find_by(id: non_existing_record_id)).to be_nil + + get api("/projects/#{non_existing_record_id}/storage") + + expect(response).to have_gitlab_http_status(:unauthorized) + end + + it 'responds with a 403 for non-admin users trying to access a non-existent project id' do + expect(Project.find_by(id: non_existing_record_id)).to be_nil + + get api("/projects/#{non_existing_record_id}/storage", user3) + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + it_behaves_like 'custom attributes endpoints', 'projects' do let(:attributable) { project } let(:other_attributable) { project2 } diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index a12b4dc9848..1b96efeca22 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -178,10 +178,12 @@ RSpec.describe API::Repositories do expect(headers['Content-Disposition']).to eq 'inline' end - it_behaves_like 'uncached response' do - before do - get api(route, current_user) - end + it 'defines an uncached header response' do + get api(route, current_user) + + expect(response.headers["Cache-Control"]).to eq("max-age=0, private, must-revalidate, no-store, no-cache") + expect(response.headers["Pragma"]).to eq("no-cache") + expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT") end context 'when sha does not exist' do diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb index 60d82f7e92a..f6339d7343c 100644 --- a/spec/support/database_cleaner.rb +++ b/spec/support/database_cleaner.rb @@ -35,8 +35,6 @@ RSpec.configure do |config| puts "Recreating the database" start = Gitlab::Metrics::System.monotonic_time - ActiveRecord::AdvisoryLockBase.clear_all_connections! - ActiveRecord::Tasks::DatabaseTasks.drop_current ActiveRecord::Tasks::DatabaseTasks.create_current ActiveRecord::Tasks::DatabaseTasks.load_schema_current diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb index bdfeb7a97f0..9af35c189d0 100644 --- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb @@ -298,7 +298,7 @@ RSpec.shared_examples 'wiki controller actions' do expect(response.headers['Content-Disposition']).to match(/^inline/) expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq('true') expect(response.cache_control[:public]).to be(false) - expect(response.cache_control[:extras]).to include('no-store') + expect(response.headers['Cache-Control']).to eq('no-store') end end end diff --git a/spec/support/shared_examples/uncached_response_shared_examples.rb b/spec/support/shared_examples/uncached_response_shared_examples.rb deleted file mode 100644 index 3997017ff35..00000000000 --- a/spec/support/shared_examples/uncached_response_shared_examples.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true -# -# Pairs with lib/gitlab/no_cache_headers.rb -# - -RSpec.shared_examples 'uncached response' do - it 'defines an uncached header response' do - expect(response.headers["Cache-Control"]).to include("no-store", "no-cache") - expect(response.headers["Pragma"]).to eq("no-cache") - expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT") - end -end diff --git a/spec/views/shared/nav/_sidebar.html.haml_spec.rb b/spec/views/shared/nav/_sidebar.html.haml_spec.rb index cf9452ba68c..2eeebdff7a8 100644 --- a/spec/views/shared/nav/_sidebar.html.haml_spec.rb +++ b/spec/views/shared/nav/_sidebar.html.haml_spec.rb @@ -25,13 +25,11 @@ RSpec.describe 'shared/nav/_sidebar.html.haml' do context 'when sidebar does not have a scope menu' do let(:scope_menu_view_path) { 'shared/nav/' } let(:scope_menu_view_name) { 'scope_menu.html.haml' } - let(:scope_menu_view) { "#{scope_menu_view_path}#{scope_menu_view_name}" } let(:scope_menu_partial) { "#{scope_menu_view_path}_#{scope_menu_view_name}" } let(:content) { 'Custom test content' } context 'when sidebar has a custom scope menu partial defined' do it 'renders the custom partial' do - allow(sidebar).to receive(:render_raw_scope_menu_partial).and_return(scope_menu_view) allow(view).to receive(:scope_menu).and_return(nil) stub_template(scope_menu_partial => content) |