diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-21 00:14:18 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-21 00:14:18 +0300 |
commit | 76365117183054e95bbb2f3a6392ac333628eacf (patch) | |
tree | 579d45ed571d5f65ae0966e2b2badf91cb430cb1 /spec | |
parent | 39cb2fdf01699eb5ac000c918f469c58dc75f7e8 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
12 files changed, 296 insertions, 77 deletions
diff --git a/spec/features/boards/board_filters_spec.rb b/spec/features/boards/board_filters_spec.rb index 49375e4b37b..783b701991b 100644 --- a/spec/features/boards/board_filters_spec.rb +++ b/spec/features/boards/board_filters_spec.rb @@ -7,8 +7,8 @@ RSpec.describe 'Issue board filters', :js do let_it_be(:user) { create(:user) } let_it_be(:board) { create(:board, project: project) } let_it_be(:project_label) { create(:label, project: project, title: 'Label') } - let_it_be(:milestone_1) { create(:milestone, project: project) } - let_it_be(:milestone_2) { create(:milestone, project: project) } + let_it_be(:milestone_1) { create(:milestone, project: project, due_date: 3.days.from_now ) } + let_it_be(:milestone_2) { create(:milestone, project: project, due_date: Date.tomorrow ) } let_it_be(:release) { create(:release, tag: 'v1.0', project: project, milestones: [milestone_1]) } let_it_be(:release_2) { create(:release, tag: 'v2.0', project: project, milestones: [milestone_2]) } let_it_be(:issue_1) { create(:issue, project: project, milestone: milestone_1, author: user) } @@ -134,8 +134,11 @@ RSpec.describe 'Issue board filters', :js do expect(filter_dropdown).to have_content('Any') expect(filter_dropdown).to have_content('Started') expect(filter_dropdown).to have_content('Upcoming') - expect(filter_dropdown).to have_content(milestone_1.title) - expect(filter_dropdown).to have_content(milestone_2.title) + + dropdown_nodes = page.find_all('.gl-filtered-search-suggestion-list > .gl-filtered-search-suggestion') + + expect(dropdown_nodes[4]).to have_content(milestone_2.title) + expect(dropdown_nodes.last).to have_content(milestone_1.title) click_on milestone_1.title filter_submit.click diff --git a/spec/frontend/integrations/edit/components/dynamic_field_spec.js b/spec/frontend/integrations/edit/components/dynamic_field_spec.js index b0fb94d2b29..ee2f6541b03 100644 --- a/spec/frontend/integrations/edit/components/dynamic_field_spec.js +++ b/spec/frontend/integrations/edit/components/dynamic_field_spec.js @@ -2,22 +2,14 @@ import { GlFormGroup, GlFormCheckbox, GlFormInput, GlFormSelect, GlFormTextarea import { mount } from '@vue/test-utils'; import DynamicField from '~/integrations/edit/components/dynamic_field.vue'; +import { mockField } from '../mock_data'; describe('DynamicField', () => { let wrapper; - const defaultProps = { - help: 'The URL of the project', - name: 'project_url', - placeholder: 'https://jira.example.com', - title: 'Project URL', - type: 'text', - value: '1', - }; - const createComponent = (props, isInheriting = false) => { wrapper = mount(DynamicField, { - propsData: { ...defaultProps, ...props }, + propsData: { ...mockField, ...props }, computed: { isInheriting: () => isInheriting, }, @@ -61,7 +53,7 @@ describe('DynamicField', () => { }); it(`renders GlFormCheckbox with correct text content when checkboxLabel is ${checkboxLabel}`, () => { - expect(findGlFormCheckbox().text()).toContain(checkboxLabel ?? defaultProps.title); + expect(findGlFormCheckbox().text()).toContain(checkboxLabel ?? mockField.title); }); it('does not render other types of input', () => { @@ -160,7 +152,7 @@ describe('DynamicField', () => { type: 'text', id: 'service_project_url', name: 'service[project_url]', - placeholder: defaultProps.placeholder, + placeholder: mockField.placeholder, required: 'required', }); expect(findGlFormInput().attributes('readonly')).toBe(readonly); @@ -179,7 +171,7 @@ describe('DynamicField', () => { it('renders description with help text', () => { createComponent(); - expect(findGlFormGroup().find('small').text()).toBe(defaultProps.help); + expect(findGlFormGroup().find('small').text()).toBe(mockField.help); }); describe('when type is checkbox', () => { @@ -189,7 +181,7 @@ describe('DynamicField', () => { }); expect(findGlFormGroup().find('small').exists()).toBe(false); - expect(findGlFormCheckbox().text()).toContain(defaultProps.help); + expect(findGlFormCheckbox().text()).toContain(mockField.help); }); }); @@ -221,40 +213,36 @@ describe('DynamicField', () => { it('renders label with title', () => { createComponent(); - expect(findGlFormGroup().find('label').text()).toBe(defaultProps.title); + expect(findGlFormGroup().find('label').text()).toBe(mockField.title); }); }); - describe('validations', () => { - describe('password field', () => { - beforeEach(() => { + describe('password field validations', () => { + describe('without value', () => { + it('requires validation', () => { createComponent({ type: 'password', required: true, value: null, + isValidated: true, }); - wrapper.vm.validated = true; - }); - - describe('without value', () => { - it('requires validation', () => { - expect(wrapper.vm.valid).toBe(false); - expect(findGlFormGroup().classes('is-invalid')).toBe(true); - expect(findGlFormInput().classes('is-invalid')).toBe(true); - }); + expect(findGlFormGroup().classes('is-invalid')).toBe(true); + expect(findGlFormInput().classes('is-invalid')).toBe(true); }); + }); - describe('with value', () => { - beforeEach(() => { - wrapper.setProps({ value: 'true' }); + describe('with value', () => { + it('does not require validation', () => { + createComponent({ + type: 'password', + required: true, + value: 'test value', + isValidated: true, }); - it('does not require validation', () => { - expect(wrapper.vm.valid).toBe(true); - expect(findGlFormGroup().classes('is-valid')).toBe(true); - expect(findGlFormInput().classes('is-valid')).toBe(true); - }); + expect(findGlFormGroup().classes('is-valid')).toBe(true); + expect(findGlFormInput().classes('is-valid')).toBe(true); }); }); }); diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js index 8cf8a403e5d..2f123508c06 100644 --- a/spec/frontend/integrations/edit/components/integration_form_spec.js +++ b/spec/frontend/integrations/edit/components/integration_form_spec.js @@ -17,16 +17,13 @@ import TriggerFields from '~/integrations/edit/components/trigger_fields.vue'; import { integrationLevels, I18N_SUCCESSFUL_CONNECTION_MESSAGE, - VALIDATE_INTEGRATION_FORM_EVENT, I18N_DEFAULT_ERROR_MESSAGE, } from '~/integrations/constants'; import { createStore } from '~/integrations/edit/store'; -import eventHub from '~/integrations/edit/event_hub'; import httpStatus from '~/lib/utils/http_status'; import { refreshCurrentPage } from '~/lib/utils/url_utility'; -import { mockIntegrationProps } from '../mock_data'; +import { mockIntegrationProps, mockField } from '../mock_data'; -jest.mock('~/integrations/edit/event_hub'); jest.mock('@sentry/browser'); jest.mock('~/lib/utils/url_utility'); @@ -97,6 +94,7 @@ describe('IntegrationForm', () => { const findGlForm = () => wrapper.findComponent(GlForm); const findRedirectToField = () => wrapper.findByTestId('redirect-to-field'); const findFormElement = () => (vueIntegrationFormFeatureFlag ? findGlForm().element : mockForm); + const findDynamicField = () => wrapper.findComponent(DynamicField); const mockFormFunctions = ({ checkValidityReturn }) => { jest.spyOn(findFormElement(), 'checkValidity').mockReturnValue(checkValidityReturn); @@ -490,6 +488,7 @@ describe('IntegrationForm', () => { showActive: true, canTest: true, initialActivated: true, + fields: [mockField], }, mountFn: mountExtended, }); @@ -510,27 +509,28 @@ describe('IntegrationForm', () => { expect(findTestButton().props('disabled')).toBe(false); }); - it('emits `VALIDATE_INTEGRATION_FORM_EVENT`', () => { - expect(eventHub.$emit).toHaveBeenCalledWith(VALIDATE_INTEGRATION_FORM_EVENT); + it('sets `isValidated` props on form fields', () => { + expect(findDynamicField().props('isValidated')).toBe(true); }); }); }); describe('when `test` button is clicked', () => { describe('when form is invalid', () => { - it('emits `VALIDATE_INTEGRATION_FORM_EVENT` event to the event hub', () => { + it('sets `isValidated` props on form fields', async () => { createComponent({ customStateProps: { showActive: true, canTest: true, + fields: [mockField], }, mountFn: mountExtended, }); mockFormFunctions({ checkValidityReturn: false }); - findTestButton().vm.$emit('click', new Event('click')); + await findTestButton().vm.$emit('click', new Event('click')); - expect(eventHub.$emit).toHaveBeenCalledWith(VALIDATE_INTEGRATION_FORM_EVENT); + expect(findDynamicField().props('isValidated')).toBe(true); }); }); diff --git a/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js index b5a8eed3598..18afbf25bf0 100644 --- a/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js +++ b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js @@ -1,9 +1,7 @@ import { GlFormCheckbox, GlFormInput } from '@gitlab/ui'; import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { VALIDATE_INTEGRATION_FORM_EVENT } from '~/integrations/constants'; import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue'; -import eventHub from '~/integrations/edit/event_hub'; import { createStore } from '~/integrations/edit/store'; describe('JiraIssuesFields', () => { @@ -222,7 +220,7 @@ describe('JiraIssuesFields', () => { }); describe('Project key input field', () => { - beforeEach(() => { + it('sets Project Key `state` attribute to `true` by default', () => { createComponent({ props: { initialProjectKey: '', @@ -230,29 +228,32 @@ describe('JiraIssuesFields', () => { }, mountFn: shallowMountExtended, }); - }); - it('sets Project Key `state` attribute to `true` by default', () => { assertProjectKeyState('true'); }); - describe('when event hub recieves `VALIDATE_INTEGRATION_FORM_EVENT` event', () => { + describe('when `isValidated` prop is true', () => { + beforeEach(() => { + createComponent({ + props: { + initialProjectKey: '', + initialEnableJiraIssues: true, + isValidated: true, + }, + mountFn: shallowMountExtended, + }); + }); + describe('with no project key', () => { it('sets Project Key `state` attribute to `undefined`', async () => { - eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT); - await wrapper.vm.$nextTick(); - assertProjectKeyState(undefined); }); }); describe('when project key is set', () => { it('sets Project Key `state` attribute to `true`', async () => { - eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT); - // set the project key await findProjectKey().vm.$emit('input', 'AB'); - await wrapper.vm.$nextTick(); assertProjectKeyState('true'); }); diff --git a/spec/frontend/integrations/edit/mock_data.js b/spec/frontend/integrations/edit/mock_data.js index 3c45ed0fb1b..39e5f8521e8 100644 --- a/spec/frontend/integrations/edit/mock_data.js +++ b/spec/frontend/integrations/edit/mock_data.js @@ -20,3 +20,12 @@ export const mockJiraIssueTypes = [ { id: '2', name: 'bug', description: 'bug' }, { id: '3', name: 'epic', description: 'epic' }, ]; + +export const mockField = { + help: 'The URL of the project', + name: 'project_url', + placeholder: 'https://jira.example.com', + title: 'Project URL', + type: 'text', + value: '1', +}; diff --git a/spec/frontend/jobs/components/table/cells/actions_cell_spec.js b/spec/frontend/jobs/components/table/cells/actions_cell_spec.js index 6caf36e1461..263698e94e1 100644 --- a/spec/frontend/jobs/components/table/cells/actions_cell_spec.js +++ b/spec/frontend/jobs/components/table/cells/actions_cell_spec.js @@ -1,13 +1,16 @@ import { GlModal } from '@gitlab/ui'; import { nextTick } from 'vue'; +import waitForPromises from 'helpers/wait_for_promises'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import ActionsCell from '~/jobs/components/table/cells/actions_cell.vue'; import JobPlayMutation from '~/jobs/components/table/graphql/mutations/job_play.mutation.graphql'; import JobRetryMutation from '~/jobs/components/table/graphql/mutations/job_retry.mutation.graphql'; import JobUnscheduleMutation from '~/jobs/components/table/graphql/mutations/job_unschedule.mutation.graphql'; +import JobCancelMutation from '~/jobs/components/table/graphql/mutations/job_cancel.mutation.graphql'; import { playableJob, retryableJob, + cancelableJob, scheduledJob, cannotRetryJob, cannotPlayJob, @@ -20,6 +23,7 @@ describe('Job actions cell', () => { const findRetryButton = () => wrapper.findByTestId('retry'); const findPlayButton = () => wrapper.findByTestId('play'); + const findCancelButton = () => wrapper.findByTestId('cancel-button'); const findDownloadArtifactsButton = () => wrapper.findByTestId('download-artifacts'); const findCountdownButton = () => wrapper.findByTestId('countdown'); const findPlayScheduledJobButton = () => wrapper.findByTestId('play-scheduled'); @@ -32,6 +36,7 @@ describe('Job actions cell', () => { data: { JobUnscheduleMutation: { jobId: scheduledJob.id } }, }; const MUTATION_SUCCESS_PLAY = { data: { JobPlayMutation: { jobId: playableJob.id } } }; + const MUTATION_SUCCESS_CANCEL = { data: { JobCancelMutation: { jobId: cancelableJob.id } } }; const $toast = { show: jest.fn(), @@ -88,6 +93,7 @@ describe('Job actions cell', () => { ${findPlayButton} | ${'play'} | ${playableJob} ${findRetryButton} | ${'retry'} | ${retryableJob} ${findDownloadArtifactsButton} | ${'download artifacts'} | ${playableJob} + ${findCancelButton} | ${'cancel'} | ${cancelableJob} `('displays the $action button', ({ button, jobType }) => { createComponent(jobType); @@ -95,9 +101,10 @@ describe('Job actions cell', () => { }); it.each` - button | mutationResult | action | jobType | mutationFile - ${findPlayButton} | ${MUTATION_SUCCESS_PLAY} | ${'play'} | ${playableJob} | ${JobPlayMutation} - ${findRetryButton} | ${MUTATION_SUCCESS} | ${'retry'} | ${retryableJob} | ${JobRetryMutation} + button | mutationResult | action | jobType | mutationFile + ${findPlayButton} | ${MUTATION_SUCCESS_PLAY} | ${'play'} | ${playableJob} | ${JobPlayMutation} + ${findRetryButton} | ${MUTATION_SUCCESS} | ${'retry'} | ${retryableJob} | ${JobRetryMutation} + ${findCancelButton} | ${MUTATION_SUCCESS_CANCEL} | ${'cancel'} | ${cancelableJob} | ${JobCancelMutation} `('performs the $action mutation', ({ button, mutationResult, jobType, mutationFile }) => { createComponent(jobType, mutationResult); @@ -111,6 +118,24 @@ describe('Job actions cell', () => { }); }); + it.each` + button | action | jobType + ${findPlayButton} | ${'play'} | ${playableJob} + ${findRetryButton} | ${'retry'} | ${retryableJob} + ${findCancelButton} | ${'cancel'} | ${cancelableJob} + ${findUnscheduleButton} | ${'unschedule'} | ${scheduledJob} + `('disables the $action button after first request', async ({ button, jobType }) => { + createComponent(jobType); + + expect(button().props('disabled')).toBe(false); + + button().vm.$emit('click'); + + await waitForPromises(); + + expect(button().props('disabled')).toBe(true); + }); + describe('Scheduled Jobs', () => { const today = () => new Date('2021-08-31'); diff --git a/spec/frontend/jobs/mock_data.js b/spec/frontend/jobs/mock_data.js index 45d297ba364..4aabefc1f66 100644 --- a/spec/frontend/jobs/mock_data.js +++ b/spec/frontend/jobs/mock_data.js @@ -1653,6 +1653,65 @@ export const retryableJob = { __typename: 'CiJob', }; +export const cancelableJob = { + artifacts: { + nodes: [], + __typename: 'CiJobArtifactConnection', + }, + allowFailure: false, + status: 'PENDING', + scheduledAt: null, + manualJob: false, + triggered: null, + createdByTag: false, + detailedStatus: { + id: 'pending-1305-1305', + detailsPath: '/root/lots-of-jobs-project/-/jobs/1305', + group: 'pending', + icon: 'status_pending', + label: 'pending', + text: 'pending', + tooltip: 'pending', + action: { + id: 'Ci::Build-pending-1305', + buttonTitle: 'Cancel this job', + icon: 'cancel', + method: 'post', + path: '/root/lots-of-jobs-project/-/jobs/1305/cancel', + title: 'Cancel', + __typename: 'StatusAction', + }, + __typename: 'DetailedStatus', + }, + id: 'gid://gitlab/Ci::Build/1305', + refName: 'main', + refPath: '/root/lots-of-jobs-project/-/commits/main', + tags: [], + shortSha: '750605f2', + commitPath: '/root/lots-of-jobs-project/-/commit/750605f29530778cf0912779eba6d073128962a5', + stage: { + id: 'gid://gitlab/Ci::Stage/181', + name: 'deploy', + __typename: 'CiStage', + }, + name: 'job_212', + duration: null, + finishedAt: null, + coverage: null, + retryable: false, + playable: false, + cancelable: true, + active: true, + stuck: false, + userPermissions: { + readBuild: true, + readJobArtifacts: true, + updateBuild: true, + __typename: 'JobPermissions', + }, + __typename: 'CiJob', +}; + export const cannotRetryJob = { ...retryableJob, userPermissions: { readBuild: true, updateBuild: false, __typename: 'JobPermissions' }, diff --git a/spec/graphql/resolvers/package_pipelines_resolver_spec.rb b/spec/graphql/resolvers/package_pipelines_resolver_spec.rb index 024f96e7158..892dc641201 100644 --- a/spec/graphql/resolvers/package_pipelines_resolver_spec.rb +++ b/spec/graphql/resolvers/package_pipelines_resolver_spec.rb @@ -15,8 +15,9 @@ RSpec.describe Resolvers::PackagePipelinesResolver do subject { resolve(described_class, obj: package, args: args, ctx: { current_user: user }) } before do - package.pipelines = pipelines - package.save! + pipelines.each do |pipeline| + create(:package_build_info, package: package, pipeline: pipeline) + end end it { is_expected.to contain_exactly(*pipelines) } diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_cross_database_modification_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_cross_database_modification_spec.rb index c41b4eeea10..ebd3c5f6235 100644 --- a/spec/lib/gitlab/database/query_analyzers/prevent_cross_database_modification_spec.rb +++ b/spec/lib/gitlab/database/query_analyzers/prevent_cross_database_modification_spec.rb @@ -14,23 +14,25 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModificatio Gitlab::Database::QueryAnalyzer.instance.within { example.run } end - shared_examples 'successful examples' do + shared_examples 'successful examples' do |model:| + let(:model) { model } + context 'outside transaction' do it { expect { run_queries }.not_to raise_error } end - context 'within transaction' do + context "within #{model} transaction" do it do - Project.transaction do + model.transaction do expect { run_queries }.not_to raise_error end end end - context 'within nested transaction' do + context "within nested #{model} transaction" do it do - Project.transaction(requires_new: true) do - Project.transaction(requires_new: true) do + model.transaction(requires_new: true) do + model.transaction(requires_new: true) do expect { run_queries }.not_to raise_error end end @@ -38,13 +40,26 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModificatio end end + shared_examples 'cross-database modification errors' do |model:| + let(:model) { model } + + context "within #{model} transaction" do + it 'raises error' do + model.transaction do + expect { run_queries }.to raise_error /Cross-database data modification/ + end + end + end + end + context 'when CI and other tables are read in a transaction' do def run_queries pipeline.reload project.reload end - include_examples 'successful examples' + include_examples 'successful examples', model: Project + include_examples 'successful examples', model: Ci::Pipeline end context 'when only CI data is modified' do @@ -53,7 +68,9 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModificatio project.reload end - include_examples 'successful examples' + include_examples 'successful examples', model: Ci::Pipeline + + include_examples 'cross-database modification errors', model: Project end context 'when other data is modified' do @@ -62,7 +79,9 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModificatio project.touch end - include_examples 'successful examples' + include_examples 'successful examples', model: Project + + include_examples 'cross-database modification errors', model: Ci::Pipeline end context 'when both CI and other data is modified' do @@ -144,7 +163,9 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModificatio project.save! end - include_examples 'successful examples' + include_examples 'successful examples', model: Ci::Pipeline + + include_examples 'cross-database modification errors', model: Project end describe '.allow_cross_database_modification_within_transaction' do diff --git a/spec/migrations/populate_audit_event_streaming_verification_token_spec.rb b/spec/migrations/populate_audit_event_streaming_verification_token_spec.rb new file mode 100644 index 00000000000..b3fe1776183 --- /dev/null +++ b/spec/migrations/populate_audit_event_streaming_verification_token_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe PopulateAuditEventStreamingVerificationToken do + let(:groups) { table(:namespaces) } + let(:destinations) { table(:audit_events_external_audit_event_destinations) } + let(:migration) { described_class.new } + + let!(:group) { groups.create!(name: 'test-group', path: 'test-group') } + let!(:destination) { destinations.create!(namespace_id: group.id, destination_url: 'https://example.com/destination', verification_token: nil) } + + describe '#up' do + it 'adds verification tokens to records created before the migration' do + expect do + migrate! + destination.reload + end.to change { destination.verification_token }.from(nil).to(a_string_matching(/\w{24}/)) + end + end +end diff --git a/spec/models/concerns/cross_database_modification_spec.rb b/spec/models/concerns/cross_database_modification_spec.rb new file mode 100644 index 00000000000..72544536953 --- /dev/null +++ b/spec/models/concerns/cross_database_modification_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe CrossDatabaseModification do + describe '.transaction' do + context 'feature flag disabled' do + before do + stub_feature_flags(track_gitlab_schema_in_current_transaction: false) + end + + it 'does not add to gitlab_transactions_stack' do + ApplicationRecord.transaction do + expect(ApplicationRecord.gitlab_transactions_stack).to be_empty + + Project.first + end + + expect(ApplicationRecord.gitlab_transactions_stack).to be_empty + end + end + + context 'feature flag is not yet setup' do + before do + allow(Feature::FlipperFeature).to receive(:table_exists?).and_raise(ActiveRecord::NoDatabaseError) + end + + it 'does not add to gitlab_transactions_stack' do + ApplicationRecord.transaction do + expect(ApplicationRecord.gitlab_transactions_stack).to be_empty + + Project.first + end + + expect(ApplicationRecord.gitlab_transactions_stack).to be_empty + end + end + + it 'adds the current gitlab schema to gitlab_transactions_stack', :aggregate_failures do + ApplicationRecord.transaction do + expect(ApplicationRecord.gitlab_transactions_stack).to contain_exactly(:gitlab_main) + + Project.first + end + + expect(ApplicationRecord.gitlab_transactions_stack).to be_empty + + Ci::ApplicationRecord.transaction do + expect(ApplicationRecord.gitlab_transactions_stack).to contain_exactly(:gitlab_ci) + + Project.first + end + + expect(ApplicationRecord.gitlab_transactions_stack).to be_empty + + Project.transaction do + expect(ApplicationRecord.gitlab_transactions_stack).to contain_exactly(:gitlab_main) + + Project.first + end + + expect(ApplicationRecord.gitlab_transactions_stack).to be_empty + + Ci::Pipeline.transaction do + expect(ApplicationRecord.gitlab_transactions_stack).to contain_exactly(:gitlab_ci) + + Project.first + end + + expect(ApplicationRecord.gitlab_transactions_stack).to be_empty + + ApplicationRecord.transaction do + expect(ApplicationRecord.gitlab_transactions_stack).to contain_exactly(:gitlab_main) + + Ci::Pipeline.transaction do + expect(ApplicationRecord.gitlab_transactions_stack).to contain_exactly(:gitlab_main, :gitlab_ci) + + Project.first + end + end + + expect(ApplicationRecord.gitlab_transactions_stack).to be_empty + end + + it 'yields' do + expect { |block| ApplicationRecord.transaction(&block) }.to yield_control + end + end +end diff --git a/spec/requests/api/graphql/packages/package_spec.rb b/spec/requests/api/graphql/packages/package_spec.rb index 2ff3bc7cc47..a9b7149d7c4 100644 --- a/spec/requests/api/graphql/packages/package_spec.rb +++ b/spec/requests/api/graphql/packages/package_spec.rb @@ -145,8 +145,9 @@ RSpec.describe 'package details' do let(:pipeline_gids) { pipelines.sort_by(&:id).map(&:to_gid).map(&:to_s).reverse } before do - composer_package.pipelines = pipelines - composer_package.save! + pipelines.each do |pipeline| + create(:package_build_info, package: composer_package, pipeline: pipeline) + end end def run_query(args) |