diff options
| author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-15 18:14:39 +0000 |
|---|---|---|
| committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-15 18:14:39 +0000 |
| commit | 0f50c47cd7f7b88cc61e954d601b90fe7d12aac3 (patch) | |
| tree | 8f376caa478ba32e57b665307970769ad7eecc34 /spec | |
| parent | 78cfc7cf4afe0c7ffb72b8a9522b07873cd36820 (diff) | |
| download | gitlab-ce-0f50c47cd7f7b88cc61e954d601b90fe7d12aac3.tar.gz | |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
| -rw-r--r-- | spec/frontend/environments/deployment_spec.js | 22 | ||||
| -rw-r--r-- | spec/frontend/fixtures/runner.rb | 17 | ||||
| -rw-r--r-- | spec/frontend/runner/components/cells/link_cell_spec.js | 72 | ||||
| -rw-r--r-- | spec/frontend/runner/components/runner_details_spec.js | 42 | ||||
| -rw-r--r-- | spec/frontend/runner/components/runner_jobs_spec.js | 156 | ||||
| -rw-r--r-- | spec/frontend/runner/components/runner_jobs_table_spec.js | 119 | ||||
| -rw-r--r-- | spec/frontend/runner/components/runner_update_form_spec.js | 3 | ||||
| -rw-r--r-- | spec/frontend/runner/mock_data.js | 2 | ||||
| -rw-r--r-- | spec/lib/gitlab/ci/lint_spec.rb | 98 | ||||
| -rw-r--r-- | spec/lib/gitlab/ci/pipeline/logger_spec.rb | 29 | ||||
| -rw-r--r-- | spec/lib/gitlab/database/loose_foreign_keys_spec.rb | 9 | ||||
| -rw-r--r-- | spec/lib/gitlab/database_spec.rb | 28 | ||||
| -rw-r--r-- | spec/migrations/20220128155814_fix_approval_rules_code_owners_rule_type_index_spec.rb | 33 | ||||
| -rw-r--r-- | spec/migrations/update_default_scan_method_of_dast_site_profile_spec.rb | 32 | ||||
| -rw-r--r-- | spec/tasks/gitlab/db_rake_spec.rb | 38 |
15 files changed, 697 insertions, 3 deletions
diff --git a/spec/frontend/environments/deployment_spec.js b/spec/frontend/environments/deployment_spec.js index 8936eb8fd64..6cc363e000b 100644 --- a/spec/frontend/environments/deployment_spec.js +++ b/spec/frontend/environments/deployment_spec.js @@ -213,8 +213,28 @@ describe('~/environments/components/deployment.vue', () => { expect(job.attributes('href')).toBe(deployment.deployable.buildPath); const apiBadge = wrapper.findByText(__('API')); expect(apiBadge.exists()).toBe(false); + + const branchLabel = wrapper.findByText(__('Branch')); + expect(branchLabel.exists()).toBe(true); + const tagLabel = wrapper.findByText(__('Tag')); + expect(tagLabel.exists()).toBe(false); + const ref = wrapper.findByRole('link', { name: deployment.ref.name }); + expect(ref.attributes('href')).toBe(deployment.ref.refPath); }); }); + + describe('with tagged deployment', () => { + beforeEach(async () => { + wrapper = createWrapper({ propsData: { deployment: { ...deployment, tag: true } } }); + await wrapper.findComponent({ ref: 'details-toggle' }).trigger('click'); + }); + + it('shows tag instead of branch', () => { + const refLabel = wrapper.findByText(__('Tag')); + expect(refLabel.exists()).toBe(true); + }); + }); + describe('with API deployment', () => { beforeEach(async () => { wrapper = createWrapper({ propsData: { deployment: { ...deployment, deployable: null } } }); @@ -237,7 +257,7 @@ describe('~/environments/components/deployment.vue', () => { }); it('shows a span instead of a link', () => { - const job = wrapper.findByText(deployment.deployable.name); + const job = wrapper.findByTitle(deployment.deployable.name); expect(job.attributes('href')).toBeUndefined(); }); }); diff --git a/spec/frontend/fixtures/runner.rb b/spec/frontend/fixtures/runner.rb index befb4e23b22..cdb4c3fd8ba 100644 --- a/spec/frontend/fixtures/runner.rb +++ b/spec/frontend/fixtures/runner.rb @@ -17,6 +17,7 @@ RSpec.describe 'Runner (JavaScript fixtures)' do let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], active: false, version: '2.0.0', revision: '456', description: 'Group runner', ip_address: '127.0.0.1') } let_it_be(:group_runner_2) { create(:ci_runner, :group, groups: [group], active: false, version: '2.0.0', revision: '456', description: 'Group runner 2', ip_address: '127.0.0.1') } let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project, project_2], active: false, version: '2.0.0', revision: '456', description: 'Project runner', ip_address: '127.0.0.1') } + let_it_be(:build) { create(:ci_build, runner: instance_runner) } query_path = 'runner/graphql/' fixtures_path = 'graphql/runner/' @@ -104,6 +105,22 @@ RSpec.describe 'Runner (JavaScript fixtures)' do expect_graphql_errors_to_be_empty end end + + describe GraphQL::Query, type: :request do + get_runner_jobs_query_name = 'get_runner_jobs.query.graphql' + + let_it_be(:query) do + get_graphql_query_as_string("#{query_path}#{get_runner_jobs_query_name}") + end + + it "#{fixtures_path}#{get_runner_jobs_query_name}.json" do + post_graphql(query, current_user: admin, variables: { + id: instance_runner.to_global_id.to_s + }) + + expect_graphql_errors_to_be_empty + end + end end describe do diff --git a/spec/frontend/runner/components/cells/link_cell_spec.js b/spec/frontend/runner/components/cells/link_cell_spec.js new file mode 100644 index 00000000000..a59a0eaa5d8 --- /dev/null +++ b/spec/frontend/runner/components/cells/link_cell_spec.js @@ -0,0 +1,72 @@ +import { GlLink } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import LinkCell from '~/runner/components/cells/link_cell.vue'; + +describe('LinkCell', () => { + let wrapper; + + const findGlLink = () => wrapper.find(GlLink); + const findSpan = () => wrapper.find('span'); + + const createComponent = ({ props = {}, ...options } = {}) => { + wrapper = shallowMountExtended(LinkCell, { + propsData: { + ...props, + }, + ...options, + }); + }; + + it('when an href is provided, renders a link', () => { + createComponent({ props: { href: '/url' } }); + expect(findGlLink().exists()).toBe(true); + }); + + it('when an href is not provided, renders no link', () => { + createComponent(); + expect(findGlLink().exists()).toBe(false); + }); + + describe.each` + href | findContent + ${null} | ${findSpan} + ${'/url'} | ${findGlLink} + `('When href is $href', ({ href, findContent }) => { + const content = 'My Text'; + const attrs = { foo: 'bar' }; + const listeners = { + click: jest.fn(), + }; + + beforeEach(() => { + createComponent({ + props: { href }, + slots: { + default: content, + }, + attrs, + listeners, + }); + }); + + afterAll(() => { + listeners.click.mockReset(); + }); + + it('Renders content', () => { + expect(findContent().text()).toBe(content); + }); + + it('Passes attributes', () => { + expect(findContent().attributes()).toMatchObject(attrs); + }); + + it('Passes event listeners', () => { + expect(listeners.click).toHaveBeenCalledTimes(0); + + findContent().vm.$emit('click'); + + expect(listeners.click).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/spec/frontend/runner/components/runner_details_spec.js b/spec/frontend/runner/components/runner_details_spec.js index dbc96a30750..6bf4a52a799 100644 --- a/spec/frontend/runner/components/runner_details_spec.js +++ b/spec/frontend/runner/components/runner_details_spec.js @@ -1,4 +1,4 @@ -import { GlSprintf, GlIntersperse } from '@gitlab/ui'; +import { GlSprintf, GlIntersperse, GlTab } from '@gitlab/ui'; import { createWrapper, ErrorWrapper } from '@vue/test-utils'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; @@ -8,6 +8,7 @@ import { ACCESS_LEVEL_REF_PROTECTED, ACCESS_LEVEL_NOT_PROTECTED } from '~/runner import RunnerDetails from '~/runner/components/runner_details.vue'; import RunnerDetail from '~/runner/components/runner_detail.vue'; import RunnerGroups from '~/runner/components/runner_groups.vue'; +import RunnersJobs from '~/runner/components/runner_jobs.vue'; import RunnerTags from '~/runner/components/runner_tags.vue'; import RunnerTag from '~/runner/components/runner_tag.vue'; @@ -38,6 +39,8 @@ describe('RunnerDetails', () => { }; const findDetailGroups = () => wrapper.findComponent(RunnerGroups); + const findRunnersJobs = () => wrapper.findComponent(RunnersJobs); + const findJobCountBadge = () => wrapper.findByTestId('job-count-badge'); const createComponent = ({ props = {}, mountFn = shallowMountExtended, stubs } = {}) => { wrapper = mountFn(RunnerDetails, { @@ -146,4 +149,41 @@ describe('RunnerDetails', () => { }); }); }); + + describe('Jobs tab', () => { + const stubs = { GlTab }; + + it('without a runner, shows no jobs', () => { + createComponent({ + props: { runner: null }, + stubs, + }); + + expect(findJobCountBadge().exists()).toBe(false); + expect(findRunnersJobs().exists()).toBe(false); + }); + + it('without a job count, shows no jobs count', () => { + createComponent({ + props: { + runner: { ...mockRunner, jobCount: undefined }, + }, + stubs, + }); + + expect(findJobCountBadge().exists()).toBe(false); + }); + + it('with a job count, shows jobs count', () => { + const runner = { ...mockRunner, jobCount: 3 }; + + createComponent({ + props: { runner }, + stubs, + }); + + expect(findJobCountBadge().text()).toBe('3'); + expect(findRunnersJobs().props('runner')).toBe(runner); + }); + }); }); diff --git a/spec/frontend/runner/components/runner_jobs_spec.js b/spec/frontend/runner/components/runner_jobs_spec.js new file mode 100644 index 00000000000..97339056370 --- /dev/null +++ b/spec/frontend/runner/components/runner_jobs_spec.js @@ -0,0 +1,156 @@ +import { GlSkeletonLoading } from '@gitlab/ui'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { createAlert } from '~/flash'; +import RunnerJobs from '~/runner/components/runner_jobs.vue'; +import RunnerJobsTable from '~/runner/components/runner_jobs_table.vue'; +import RunnerPagination from '~/runner/components/runner_pagination.vue'; +import { captureException } from '~/runner/sentry_utils'; +import { I18N_NO_JOBS_FOUND, RUNNER_DETAILS_JOBS_PAGE_SIZE } from '~/runner/constants'; + +import getRunnerJobsQuery from '~/runner/graphql/get_runner_jobs.query.graphql'; + +import { runnerData, runnerJobsData } from '../mock_data'; + +jest.mock('~/flash'); +jest.mock('~/runner/sentry_utils'); + +const mockRunner = runnerData.data.runner; +const mockRunnerWithJobs = runnerJobsData.data.runner; +const mockJobs = mockRunnerWithJobs.jobs.nodes; + +Vue.use(VueApollo); + +describe('RunnerJobs', () => { + let wrapper; + let mockRunnerJobsQuery; + + const findGlSkeletonLoading = () => wrapper.findComponent(GlSkeletonLoading); + const findRunnerJobsTable = () => wrapper.findComponent(RunnerJobsTable); + const findRunnerPagination = () => wrapper.findComponent(RunnerPagination); + + const createComponent = ({ mountFn = shallowMountExtended } = {}) => { + wrapper = mountFn(RunnerJobs, { + apolloProvider: createMockApollo([[getRunnerJobsQuery, mockRunnerJobsQuery]]), + propsData: { + runner: mockRunner, + }, + }); + }; + + beforeEach(() => { + mockRunnerJobsQuery = jest.fn(); + }); + + afterEach(() => { + mockRunnerJobsQuery.mockReset(); + wrapper.destroy(); + }); + + it('Requests runner jobs', async () => { + createComponent(); + + await waitForPromises(); + + expect(mockRunnerJobsQuery).toHaveBeenCalledTimes(1); + expect(mockRunnerJobsQuery).toHaveBeenCalledWith({ + id: mockRunner.id, + first: RUNNER_DETAILS_JOBS_PAGE_SIZE, + }); + }); + + describe('When there are jobs assigned', () => { + beforeEach(async () => { + mockRunnerJobsQuery.mockResolvedValueOnce(runnerJobsData); + + createComponent(); + await waitForPromises(); + }); + + it('Shows jobs', () => { + const jobs = findRunnerJobsTable().props('jobs'); + + expect(jobs).toHaveLength(mockJobs.length); + expect(jobs[0]).toMatchObject(mockJobs[0]); + }); + + describe('When "Next" page is clicked', () => { + beforeEach(async () => { + findRunnerPagination().vm.$emit('input', { page: 2, after: 'AFTER_CURSOR' }); + + await waitForPromises(); + }); + + it('A new page is requested', () => { + expect(mockRunnerJobsQuery).toHaveBeenCalledTimes(2); + expect(mockRunnerJobsQuery).toHaveBeenLastCalledWith({ + id: mockRunner.id, + first: RUNNER_DETAILS_JOBS_PAGE_SIZE, + after: 'AFTER_CURSOR', + }); + }); + }); + }); + + describe('When loading', () => { + it('shows loading indicator and no other content', () => { + createComponent(); + + expect(findGlSkeletonLoading().exists()).toBe(true); + expect(findRunnerJobsTable().exists()).toBe(false); + expect(findRunnerPagination().attributes('disabled')).toBe('true'); + }); + }); + + describe('When there are no jobs', () => { + beforeEach(async () => { + mockRunnerJobsQuery.mockResolvedValueOnce({ + data: { + runner: { + id: mockRunner.id, + projectCount: 0, + jobs: { + nodes: [], + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + }, + }, + }, + }); + + createComponent(); + await waitForPromises(); + }); + + it('Shows a "None" label', () => { + expect(wrapper.text()).toBe(I18N_NO_JOBS_FOUND); + }); + }); + + describe('When an error occurs', () => { + beforeEach(async () => { + mockRunnerJobsQuery.mockRejectedValue(new Error('Error!')); + + createComponent(); + await waitForPromises(); + }); + + it('shows an error', () => { + expect(createAlert).toHaveBeenCalled(); + }); + + it('reports an error', () => { + expect(captureException).toHaveBeenCalledWith({ + component: 'RunnerJobs', + error: expect.any(Error), + }); + }); + }); +}); diff --git a/spec/frontend/runner/components/runner_jobs_table_spec.js b/spec/frontend/runner/components/runner_jobs_table_spec.js new file mode 100644 index 00000000000..5f4905ad2a8 --- /dev/null +++ b/spec/frontend/runner/components/runner_jobs_table_spec.js @@ -0,0 +1,119 @@ +import { GlTableLite } from '@gitlab/ui'; +import { + extendedWrapper, + shallowMountExtended, + mountExtended, +} from 'helpers/vue_test_utils_helper'; +import { __, s__ } from '~/locale'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import RunnerJobsTable from '~/runner/components/runner_jobs_table.vue'; +import { useFakeDate } from 'helpers/fake_date'; +import { runnerJobsData } from '../mock_data'; + +const mockJobs = runnerJobsData.data.runner.jobs.nodes; + +describe('RunnerJobsTable', () => { + let wrapper; + const mockNow = '2021-01-15T12:00:00Z'; + const mockOneHourAgo = '2021-01-15T11:00:00Z'; + + useFakeDate(mockNow); + + const findTable = () => wrapper.findComponent(GlTableLite); + const findHeaders = () => wrapper.findAll('th'); + const findRows = () => wrapper.findAll('[data-testid^="job-row-"]'); + const findCell = ({ field }) => + extendedWrapper(findRows().at(0).find(`[data-testid="td-${field}"]`)); + + const createComponent = ({ props = {} } = {}, mountFn = shallowMountExtended) => { + wrapper = mountFn(RunnerJobsTable, { + propsData: { + jobs: mockJobs, + ...props, + }, + stubs: { + GlTableLite, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('Sets job id as a row key', () => { + createComponent(); + + expect(findTable().attributes('primarykey')).toBe('id'); + }); + + describe('Table data', () => { + beforeEach(() => { + createComponent({}, mountExtended); + }); + + it('Displays headers', () => { + const headerLabels = findHeaders().wrappers.map((w) => w.text()); + + expect(headerLabels).toEqual([ + s__('Job|Status'), + __('Job'), + __('Project'), + __('Commit'), + s__('Job|Finished at'), + s__('Runners|Tags'), + ]); + }); + + it('Displays a list of jobs', () => { + expect(findRows()).toHaveLength(1); + }); + + it('Displays details of a job', () => { + const { id, detailedStatus, pipeline, shortSha, commitPath } = mockJobs[0]; + + expect(findCell({ field: 'status' }).text()).toMatchInterpolatedText(detailedStatus.text); + + expect(findCell({ field: 'job' }).text()).toContain(`#${getIdFromGraphQLId(id)}`); + expect(findCell({ field: 'job' }).find('a').attributes('href')).toBe( + detailedStatus.detailsPath, + ); + + expect(findCell({ field: 'project' }).text()).toBe(pipeline.project.name); + expect(findCell({ field: 'project' }).find('a').attributes('href')).toBe( + pipeline.project.webUrl, + ); + + expect(findCell({ field: 'commit' }).text()).toBe(shortSha); + expect(findCell({ field: 'commit' }).find('a').attributes('href')).toBe(commitPath); + }); + }); + + describe('Table data formatting', () => { + let mockJobsCopy; + + beforeEach(() => { + mockJobsCopy = [ + { + ...mockJobs[0], + }, + ]; + }); + + it('Formats finishedAt time', () => { + mockJobsCopy[0].finishedAt = mockOneHourAgo; + + createComponent({ props: { jobs: mockJobsCopy } }, mountExtended); + + expect(findCell({ field: 'finished_at' }).text()).toBe('1 hour ago'); + }); + + it('Formats tags', () => { + mockJobsCopy[0].tags = ['tag-1', 'tag-2']; + + createComponent({ props: { jobs: mockJobsCopy } }, mountExtended); + + expect(findCell({ field: 'tags' }).text()).toMatchInterpolatedText('tag-1 tag-2'); + }); + }); +}); diff --git a/spec/frontend/runner/components/runner_update_form_spec.js b/spec/frontend/runner/components/runner_update_form_spec.js index 36120a4c7ed..8b76be396ef 100644 --- a/spec/frontend/runner/components/runner_update_form_spec.js +++ b/spec/frontend/runner/components/runner_update_form_spec.js @@ -123,6 +123,7 @@ describe('RunnerUpdateForm', () => { // Some read-only fields are not submitted const { + __typename, ipAddress, runnerType, createdAt, @@ -132,7 +133,7 @@ describe('RunnerUpdateForm', () => { userPermissions, version, groups, - __typename, + jobCount, ...submitted } = mockRunner; diff --git a/spec/frontend/runner/mock_data.js b/spec/frontend/runner/mock_data.js index 7260f0fbc9a..d80caa47752 100644 --- a/spec/frontend/runner/mock_data.js +++ b/spec/frontend/runner/mock_data.js @@ -7,6 +7,7 @@ import runnersDataPaginated from 'test_fixtures/graphql/runner/get_runners.query import runnerData from 'test_fixtures/graphql/runner/get_runner.query.graphql.json'; import runnerWithGroupData from 'test_fixtures/graphql/runner/get_runner.query.graphql.with_group.json'; import runnerProjectsData from 'test_fixtures/graphql/runner/get_runner_projects.query.graphql.json'; +import runnerJobsData from 'test_fixtures/graphql/runner/get_runner_jobs.query.graphql.json'; // Group queries import groupRunnersData from 'test_fixtures/graphql/runner/get_group_runners.query.graphql.json'; @@ -20,6 +21,7 @@ export { runnerData, runnerWithGroupData, runnerProjectsData, + runnerJobsData, groupRunnersData, groupRunnersCountData, groupRunnersDataPaginated, diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb index 652fe0c4f94..747ff13c840 100644 --- a/spec/lib/gitlab/ci/lint_spec.rb +++ b/spec/lib/gitlab/ci/lint_spec.rb @@ -322,4 +322,102 @@ RSpec.describe Gitlab::Ci::Lint do end end end + + context 'pipeline logger' do + let(:counters) do + { + 'count' => a_kind_of(Numeric), + 'avg' => a_kind_of(Numeric), + 'max' => a_kind_of(Numeric), + 'min' => a_kind_of(Numeric) + } + end + + let(:loggable_data) do + { + 'class' => 'Gitlab::Ci::Pipeline::Logger', + 'config_build_context_duration_s' => counters, + 'config_build_variables_duration_s' => counters, + 'config_compose_duration_s' => counters, + 'config_expand_duration_s' => counters, + 'config_external_process_duration_s' => counters, + 'config_stages_inject_duration_s' => counters, + 'config_tags_resolve_duration_s' => counters, + 'config_yaml_extend_duration_s' => counters, + 'config_yaml_load_duration_s' => counters, + 'pipeline_creation_caller' => 'Gitlab::Ci::Lint', + 'pipeline_creation_service_duration_s' => a_kind_of(Numeric), + 'pipeline_persisted' => false, + 'pipeline_source' => 'unknown', + 'project_id' => project&.id, + 'yaml_process_duration_s' => counters + } + end + + let(:content) do + <<~YAML + build: + script: echo + YAML + end + + subject(:validate) { lint.validate(content, dry_run: false) } + + before do + project&.add_developer(user) + end + + context 'when the duration is under the threshold' do + it 'does not create a log entry' do + expect(Gitlab::AppJsonLogger).not_to receive(:info) + + validate + end + end + + context 'when the durations exceeds the threshold' do + let(:timer) do + proc do + @timer = @timer.to_i + 30 + end + end + + before do + allow(Gitlab::Ci::Pipeline::Logger) + .to receive(:current_monotonic_time) { timer.call } + end + + it 'creates a log entry' do + expect(Gitlab::AppJsonLogger).to receive(:info).with(loggable_data) + + validate + end + + context 'when the feature flag is disabled' do + before do + stub_feature_flags(ci_pipeline_creation_logger: false) + end + + it 'does not create a log entry' do + expect(Gitlab::AppJsonLogger).not_to receive(:info) + + validate + end + end + + context 'when project is not provided' do + let(:project) { nil } + + let(:project_nil_loggable_data) do + loggable_data.except('project_id') + end + + it 'creates a log entry without project_id' do + expect(Gitlab::AppJsonLogger).to receive(:info).with(project_nil_loggable_data) + + validate + end + end + end + end end diff --git a/spec/lib/gitlab/ci/pipeline/logger_spec.rb b/spec/lib/gitlab/ci/pipeline/logger_spec.rb index 1092824eddd..f31361431f2 100644 --- a/spec/lib/gitlab/ci/pipeline/logger_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/logger_spec.rb @@ -203,6 +203,35 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do expect(commit).to be_truthy end end + + context 'when project is not passed and pipeline is not persisted' do + let(:project) {} + let(:pipeline) { build(:ci_pipeline) } + + let(:loggable_data) do + { + 'class' => described_class.name.to_s, + 'pipeline_persisted' => false, + 'pipeline_creation_service_duration_s' => a_kind_of(Numeric), + 'pipeline_creation_caller' => 'source', + 'pipeline_save_duration_s' => { + 'avg' => 60, 'count' => 1, 'max' => 60, 'min' => 60 + }, + 'pipeline_creation_duration_s' => { + 'avg' => 20, 'count' => 2, 'max' => 30, 'min' => 10 + } + } + end + + it 'logs to application.json' do + expect(Gitlab::AppJsonLogger) + .to receive(:info) + .with(a_hash_including(loggable_data)) + .and_call_original + + expect(commit).to be_truthy + end + end end context 'when the feature flag is disabled' do diff --git a/spec/lib/gitlab/database/loose_foreign_keys_spec.rb b/spec/lib/gitlab/database/loose_foreign_keys_spec.rb index 297bcc27bcd..ed11699e494 100644 --- a/spec/lib/gitlab/database/loose_foreign_keys_spec.rb +++ b/spec/lib/gitlab/database/loose_foreign_keys_spec.rb @@ -18,6 +18,15 @@ RSpec.describe Gitlab::Database::LooseForeignKeys do )) end + context 'ensure keys are sorted' do + it 'does not have any keys that are out of order' do + parsed = YAML.parse_file(described_class.loose_foreign_keys_yaml_path) + mapping = parsed.children.first + table_names = mapping.children.select(&:scalar?).map(&:value) + expect(table_names).to eq(table_names.sort), "expected sorted table names in the YAML file" + end + end + context 'ensure no duplicates are found' do it 'does not have duplicate tables defined' do # since we use hash to detect duplicate hash keys we need to parse YAML document diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 5ec7c338a2a..b3b7c81e9e7 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -104,6 +104,34 @@ RSpec.describe Gitlab::Database do end end + describe '.check_for_non_superuser' do + subject { described_class.check_for_non_superuser } + + let(:non_superuser) { Gitlab::Database::PgUser.new(usename: 'foo', usesuper: false ) } + let(:superuser) { Gitlab::Database::PgUser.new(usename: 'bar', usesuper: true) } + + it 'prints user details if not superuser' do + allow(Gitlab::Database::PgUser).to receive(:find_by).with('usename = CURRENT_USER').and_return(non_superuser) + + expect(Gitlab::AppLogger).to receive(:info).with("Account details: User: \"foo\", UseSuper: (false)") + + subject + end + + it 'raises an exception if superuser' do + allow(Gitlab::Database::PgUser).to receive(:find_by).with('usename = CURRENT_USER').and_return(superuser) + + expect(Gitlab::AppLogger).to receive(:info).with("Account details: User: \"bar\", UseSuper: (true)") + expect { subject }.to raise_error('Error: detected superuser') + end + + it 'catches exception if find_by fails' do + allow(Gitlab::Database::PgUser).to receive(:find_by).with('usename = CURRENT_USER').and_raise(ActiveRecord::StatementInvalid) + + expect { subject }.to raise_error('User CURRENT_USER not found') + end + end + describe '.check_postgres_version_and_print_warning' do let(:reflect) { instance_spy(Gitlab::Database::Reflection) } diff --git a/spec/migrations/20220128155814_fix_approval_rules_code_owners_rule_type_index_spec.rb b/spec/migrations/20220128155814_fix_approval_rules_code_owners_rule_type_index_spec.rb new file mode 100644 index 00000000000..1558facdf96 --- /dev/null +++ b/spec/migrations/20220128155814_fix_approval_rules_code_owners_rule_type_index_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration!('fix_approval_rules_code_owners_rule_type_index') + +RSpec.describe FixApprovalRulesCodeOwnersRuleTypeIndex, :migration do + let(:table_name) { :approval_merge_request_rules } + let(:index_name) { 'index_approval_rules_code_owners_rule_type' } + + it 'correctly migrates up and down' do + reversible_migration do |migration| + migration.before -> { + expect(subject.index_exists_by_name?(table_name, index_name)).to be_truthy + } + + migration.after -> { + expect(subject.index_exists_by_name?(table_name, index_name)).to be_truthy + } + end + end + + context 'when the index already exists' do + before do + subject.add_concurrent_index table_name, :merge_request_id, where: 'rule_type = 2', name: index_name + end + + it 'keeps the index' do + migrate! + + expect(subject.index_exists_by_name?(table_name, index_name)).to be_truthy + end + end +end diff --git a/spec/migrations/update_default_scan_method_of_dast_site_profile_spec.rb b/spec/migrations/update_default_scan_method_of_dast_site_profile_spec.rb new file mode 100644 index 00000000000..b73aa7016a1 --- /dev/null +++ b/spec/migrations/update_default_scan_method_of_dast_site_profile_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe UpdateDefaultScanMethodOfDastSiteProfile do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:dast_sites) { table(:dast_sites) } + let(:dast_site_profiles) { table(:dast_site_profiles) } + + before do + namespace = namespaces.create!(name: 'test', path: 'test') + project = projects.create!(id: 12, namespace_id: namespace.id, name: 'gitlab', path: 'gitlab') + dast_site = dast_sites.create!(id: 1, url: 'https://www.gitlab.com', project_id: project.id) + + dast_site_profiles.create!(id: 1, project_id: project.id, dast_site_id: dast_site.id, + name: "#{FFaker::Product.product_name.truncate(192)} #{SecureRandom.hex(4)} - 0", + scan_method: 0, target_type: 0) + + dast_site_profiles.create!(id: 2, project_id: project.id, dast_site_id: dast_site.id, + name: "#{FFaker::Product.product_name.truncate(192)} #{SecureRandom.hex(4)} - 1", + scan_method: 0, target_type: 1) + end + + it 'updates the scan_method to 1 for profiles with target_type 1' do + migrate! + + expect(dast_site_profiles.where(scan_method: 1).count).to eq 1 + expect(dast_site_profiles.where(scan_method: 0).count).to eq 1 + end +end diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb index 4e9aac01c08..c3fd8135ae0 100644 --- a/spec/tasks/gitlab/db_rake_spec.rb +++ b/spec/tasks/gitlab/db_rake_spec.rb @@ -446,6 +446,44 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do end end + describe 'gitlab:db:reset_as_non_superuser' do + let(:connection_pool) { instance_double(ActiveRecord::ConnectionAdapters::ConnectionPool ) } + let(:connection) { instance_double(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) } + let(:configurations) { double(ActiveRecord::DatabaseConfigurations) } + let(:configuration) { instance_double(ActiveRecord::DatabaseConfigurations::HashConfig) } + let(:config_hash) { { username: 'foo' } } + + it 'migrate as nonsuperuser check with default username' do + allow(Rake::Task['db:drop']).to receive(:invoke) + allow(Rake::Task['db:create']).to receive(:invoke) + allow(ActiveRecord::Base).to receive(:configurations).and_return(configurations) + allow(configurations).to receive(:configs_for).and_return([configuration]) + allow(configuration).to receive(:configuration_hash).and_return(config_hash) + allow(ActiveRecord::Base).to receive(:establish_connection).and_return(connection_pool) + + expect(config_hash).to receive(:merge).with({ username: 'gitlab' }) + expect(Gitlab::Database).to receive(:check_for_non_superuser) + expect(Rake::Task['db:migrate']).to receive(:invoke) + + run_rake_task('gitlab:db:reset_as_non_superuser') + end + + it 'migrate as nonsuperuser check with specified username' do + allow(Rake::Task['db:drop']).to receive(:invoke) + allow(Rake::Task['db:create']).to receive(:invoke) + allow(ActiveRecord::Base).to receive(:configurations).and_return(configurations) + allow(configurations).to receive(:configs_for).and_return([configuration]) + allow(configuration).to receive(:configuration_hash).and_return(config_hash) + allow(ActiveRecord::Base).to receive(:establish_connection).and_return(connection_pool) + + expect(config_hash).to receive(:merge).with({ username: 'foo' }) + expect(Gitlab::Database).to receive(:check_for_non_superuser) + expect(Rake::Task['db:migrate']).to receive(:invoke) + + run_rake_task('gitlab:db:reset_as_non_superuser', '[foo]') + end + end + def run_rake_task(task_name, arguments = '') Rake::Task[task_name].reenable Rake.application.invoke_task("#{task_name}#{arguments}") |
