diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-16 15:13:53 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-16 15:13:53 +0300 |
commit | abed3501da6ecd7ae31cfe2b8fa7654e91a26fb6 (patch) | |
tree | 65318870e1bff62cd176e7fe5bd26155cc38c08f /spec | |
parent | 6259da15b5ede93a9f688ddd062860166e7cf21a (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r-- | spec/features/boards/new_issue_spec.rb | 19 | ||||
-rw-r--r-- | spec/features/issues/user_interacts_with_awards_spec.rb | 5 | ||||
-rw-r--r-- | spec/frontend/boards/components/board_new_issue_spec.js | 35 | ||||
-rw-r--r-- | spec/frontend/environments/canary_ingress_spec.js | 27 | ||||
-rw-r--r-- | spec/frontend/environments/deploy_board_component_spec.js | 65 | ||||
-rw-r--r-- | spec/frontend/environments/deploy_board_wrapper_spec.js | 124 | ||||
-rw-r--r-- | spec/frontend/environments/graphql/mock_data.js | 66 | ||||
-rw-r--r-- | spec/frontend/environments/new_environment_item_spec.js | 34 | ||||
-rw-r--r-- | spec/frontend/environments/new_environments_app_spec.js | 24 | ||||
-rw-r--r-- | spec/models/namespace_spec.rb | 14 | ||||
-rw-r--r-- | spec/models/project_spec.rb | 45 | ||||
-rw-r--r-- | spec/requests/api/repositories_spec.rb | 11 | ||||
-rw-r--r-- | spec/services/groups/create_service_spec.rb | 28 | ||||
-rw-r--r-- | spec/services/issues/create_service_spec.rb | 2 | ||||
-rw-r--r-- | spec/services/projects/create_service_spec.rb | 20 |
15 files changed, 465 insertions, 54 deletions
diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index f88d31bda88..5f4517d47ee 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -3,12 +3,13 @@ require 'spec_helper' RSpec.describe 'Issue Boards new issue', :js do - let_it_be(:project) { create(:project, :public) } - let_it_be(:board) { create(:board, project: project) } - let_it_be(:backlog_list) { create(:backlog_list, board: board) } - let_it_be(:label) { create(:label, project: project, name: 'Label 1') } - let_it_be(:list) { create(:list, board: board, label: label, position: 0) } - let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + let_it_be(:board) { create(:board, project: project) } + let_it_be(:backlog_list) { create(:backlog_list, board: board) } + let_it_be(:label) { create(:label, project: project, name: 'Label 1') } + let_it_be(:list) { create(:list, board: board, label: label, position: 0) } + let_it_be(:user) { create(:user) } + let_it_be(:existing_issue) { create(:issue, project: project, title: 'other issue', relative_position: 50) } let(:board_list_header) { first('[data-testid="board-list-header"]') } let(:project_select_dropdown) { find('[data-testid="project-select-dropdown"]') } @@ -56,7 +57,7 @@ RSpec.describe 'Issue Boards new issue', :js do end end - it 'creates new issue and opens sidebar' do + it 'creates new issue, places it on top of the list, and opens sidebar' do page.within(first('.board')) do click_button 'New issue' end @@ -69,12 +70,14 @@ RSpec.describe 'Issue Boards new issue', :js do wait_for_requests page.within(first('.board [data-testid="issue-count-badge"]')) do - expect(page).to have_content('1') + expect(page).to have_content('2') end page.within(first('.board-card')) do issue = project.issues.find_by_title('bug') + expect(issue.relative_position).to be < existing_issue.relative_position + expect(page).to have_content(issue.to_reference) expect(page).to have_link(issue.title, href: /#{issue_path(issue)}/) end diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb index 94f6c1e1a8f..892b57bac5c 100644 --- a/spec/features/issues/user_interacts_with_awards_spec.rb +++ b/spec/features/issues/user_interacts_with_awards_spec.rb @@ -49,7 +49,7 @@ RSpec.describe 'User interacts with awards' do end end - it 'toggles a custom award emoji', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/351878' do + it 'toggles a custom award emoji' do page.within('.awards') do page.find('.add-reaction-button').click end @@ -65,9 +65,10 @@ RSpec.describe 'User interacts with awards' do expect(page.find('[data-testid="award-button"].selected .js-counter')).to have_content('1') expect(page).to have_css('[data-testid="award-button"].selected[title="You reacted with :8ball:"]') + wait_for_requests + expect do page.find('[data-testid="award-button"].selected').click - wait_for_requests end.to change { page.all('[data-testid="award-button"]').size }.from(3).to(2) end end diff --git a/spec/frontend/boards/components/board_new_issue_spec.js b/spec/frontend/boards/components/board_new_issue_spec.js index 08ef119aad8..8b0100d069a 100644 --- a/spec/frontend/boards/components/board_new_issue_spec.js +++ b/spec/frontend/boards/components/board_new_issue_spec.js @@ -6,7 +6,7 @@ import BoardNewItem from '~/boards/components/board_new_item.vue'; import ProjectSelect from '~/boards/components/project_select.vue'; import eventHub from '~/boards/eventhub'; -import { mockList, mockGroupProjects } from '../mock_data'; +import { mockList, mockGroupProjects, mockIssue, mockIssue2 } from '../mock_data'; Vue.use(Vuex); @@ -16,7 +16,7 @@ const mockActions = { addListNewIssue: addListNewIssuesSpy }; const createComponent = ({ state = { selectedProject: mockGroupProjects[0], fullPath: mockGroupProjects[0].fullPath }, actions = mockActions, - getters = { isGroupBoard: () => true, isProjectBoard: () => false }, + getters = { isGroupBoard: () => true, getBoardItemsByList: () => () => [] }, } = {}) => shallowMount(BoardNewIssue, { store: new Vuex.Store({ @@ -75,10 +75,39 @@ describe('Issue boards new issue form', () => { assigneeIds: [], milestoneId: undefined, projectPath: mockGroupProjects[0].fullPath, + moveAfterId: undefined, }, }); }); + describe('when list has an existing issues', () => { + beforeEach(() => { + wrapper = createComponent({ + getters: { + isGroupBoard: () => true, + getBoardItemsByList: () => () => [mockIssue, mockIssue2], + }, + }); + }); + + it('it uses the first issue ID as moveAfterId', async () => { + findBoardNewItem().vm.$emit('form-submit', { title: 'Foo' }); + + await nextTick(); + expect(addListNewIssuesSpy).toHaveBeenCalledWith(expect.any(Object), { + list: mockList, + issueInput: { + title: 'Foo', + labelIds: [], + assigneeIds: [], + milestoneId: undefined, + projectPath: mockGroupProjects[0].fullPath, + moveAfterId: mockIssue.id, + }, + }); + }); + }); + it('emits event `toggle-issue-form` with current list Id suffix on eventHub when `board-new-item` emits form-cancel event', async () => { jest.spyOn(eventHub, '$emit').mockImplementation(); findBoardNewItem().vm.$emit('form-cancel'); @@ -99,7 +128,7 @@ describe('Issue boards new issue form', () => { describe('when in project issue board', () => { beforeEach(() => { wrapper = createComponent({ - getters: { isGroupBoard: () => false, isProjectBoard: () => true }, + getters: { isGroupBoard: () => false }, }); }); diff --git a/spec/frontend/environments/canary_ingress_spec.js b/spec/frontend/environments/canary_ingress_spec.js index 6c7a786e652..d58f9f9b8a2 100644 --- a/spec/frontend/environments/canary_ingress_spec.js +++ b/spec/frontend/environments/canary_ingress_spec.js @@ -3,6 +3,7 @@ import { mount } from '@vue/test-utils'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import CanaryIngress from '~/environments/components/canary_ingress.vue'; import { CANARY_UPDATE_MODAL } from '~/environments/constants'; +import { rolloutStatus } from './graphql/mock_data'; describe('/environments/components/canary_ingress.vue', () => { let wrapper; @@ -13,16 +14,18 @@ describe('/environments/components/canary_ingress.vue', () => { .at(x / 5) .vm.$emit('click'); - const createComponent = () => { + const createComponent = (props = {}, options = {}) => { wrapper = mount(CanaryIngress, { propsData: { canaryIngress: { canary_weight: 60, }, + ...props, }, directives: { GlModal: createMockDirective(), }, + ...options, }); }; @@ -94,9 +97,25 @@ describe('/environments/components/canary_ingress.vue', () => { }); it('is set to open the change modal', () => { - const options = canaryWeightDropdown.findAll(GlDropdownItem); - expect(options).toHaveLength(21); - options.wrappers.forEach((w, i) => expect(w.text()).toBe((i * 5).toString())); + canaryWeightDropdown + .findAll(GlDropdownItem) + .wrappers.forEach((w) => + expect(getBinding(w.element, 'gl-modal')).toMatchObject({ value: CANARY_UPDATE_MODAL }), + ); + }); + }); + + describe('graphql', () => { + beforeEach(() => { + createComponent({ + graphql: true, + canaryIngress: rolloutStatus.canaryIngress, + }); + }); + + it('shows the correct weight', () => { + const canaryWeightDropdown = wrapper.find('[data-testid="canary-weight"]'); + expect(canaryWeightDropdown.props('text')).toBe('50'); }); }); }); diff --git a/spec/frontend/environments/deploy_board_component_spec.js b/spec/frontend/environments/deploy_board_component_spec.js index 2033cf00cff..f0fb4d1027c 100644 --- a/spec/frontend/environments/deploy_board_component_spec.js +++ b/spec/frontend/environments/deploy_board_component_spec.js @@ -4,6 +4,7 @@ import Vue, { nextTick } from 'vue'; import CanaryIngress from '~/environments/components/canary_ingress.vue'; import DeployBoard from '~/environments/components/deploy_board.vue'; import { deployBoardMockData, environment } from './mock_data'; +import { rolloutStatus } from './graphql/mock_data'; const logsPath = `gitlab-org/gitlab-test/-/logs?environment_name=${environment.name}`; @@ -28,7 +29,7 @@ describe('Deploy Board', () => { }); it('should render percentage with completion value provided', () => { - expect(wrapper.vm.$refs.percentage.innerText).toEqual(`${deployBoardMockData.completion}%`); + expect(wrapper.find({ ref: 'percentage' }).text()).toBe(`${deployBoardMockData.completion}%`); }); it('should render total instance count', () => { @@ -57,20 +58,74 @@ describe('Deploy Board', () => { it('sets up a tooltip for the legend', () => { const iconSpan = wrapper.find('[data-testid="legend-tooltip-target"]'); - const tooltip = wrapper.find(GlTooltip); - const icon = iconSpan.find(GlIcon); + const tooltip = wrapper.findComponent(GlTooltip); + const icon = iconSpan.findComponent(GlIcon); expect(tooltip.props('target')()).toBe(iconSpan.element); expect(icon.props('name')).toBe('question'); }); it('renders the canary weight selector', () => { - const canary = wrapper.find(CanaryIngress); + const canary = wrapper.findComponent(CanaryIngress); expect(canary.exists()).toBe(true); expect(canary.props('canaryIngress')).toEqual({ canary_weight: 50 }); }); }); + describe('with new valid data', () => { + beforeEach(async () => { + wrapper = createComponent({ + graphql: true, + deployBoardData: rolloutStatus, + }); + await nextTick(); + }); + + it('should render percentage with completion value provided', () => { + expect(wrapper.find({ ref: 'percentage' }).text()).toBe(`${rolloutStatus.completion}%`); + }); + + it('should render total instance count', () => { + const renderedTotal = wrapper.find('.deploy-board-instances-text'); + const actualTotal = rolloutStatus.instances.length; + const output = `${actualTotal > 1 ? 'Instances' : 'Instance'} (${actualTotal})`; + + expect(renderedTotal.text()).toEqual(output); + }); + + it('should render all instances', () => { + const instances = wrapper.findAll('.deploy-board-instances-container a'); + + expect(instances).toHaveLength(rolloutStatus.instances.length); + expect( + instances.at(1).classes(`deployment-instance-${rolloutStatus.instances[2].status}`), + ).toBe(true); + }); + + it('should render an abort and a rollback button with the provided url', () => { + const buttons = wrapper.findAll('.deploy-board-actions a'); + + expect(buttons.at(0).attributes('href')).toEqual(rolloutStatus.rollbackUrl); + expect(buttons.at(1).attributes('href')).toEqual(rolloutStatus.abortUrl); + }); + + it('sets up a tooltip for the legend', () => { + const iconSpan = wrapper.find('[data-testid="legend-tooltip-target"]'); + const tooltip = wrapper.findComponent(GlTooltip); + const icon = iconSpan.findComponent(GlIcon); + + expect(tooltip.props('target')()).toBe(iconSpan.element); + expect(icon.props('name')).toBe('question'); + }); + + it('renders the canary weight selector', () => { + const canary = wrapper.findComponent(CanaryIngress); + expect(canary.exists()).toBe(true); + expect(canary.props('canaryIngress')).toEqual({ canaryWeight: 50 }); + expect(canary.props('graphql')).toBe(true); + }); + }); + describe('with empty state', () => { beforeEach((done) => { wrapper = createComponent({ @@ -102,7 +157,7 @@ describe('Deploy Board', () => { }); it('should render loading spinner', () => { - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); }); }); diff --git a/spec/frontend/environments/deploy_board_wrapper_spec.js b/spec/frontend/environments/deploy_board_wrapper_spec.js new file mode 100644 index 00000000000..c8e6df4d324 --- /dev/null +++ b/spec/frontend/environments/deploy_board_wrapper_spec.js @@ -0,0 +1,124 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { GlCollapse, GlIcon } from '@gitlab/ui'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { stubTransition } from 'helpers/stub_transition'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { __, s__ } from '~/locale'; +import DeployBoardWrapper from '~/environments/components/deploy_board_wrapper.vue'; +import DeployBoard from '~/environments/components/deploy_board.vue'; +import setEnvironmentToChangeCanaryMutation from '~/environments/graphql/mutations/set_environment_to_change_canary.mutation.graphql'; +import { resolvedEnvironment, rolloutStatus } from './graphql/mock_data'; + +Vue.use(VueApollo); + +describe('~/environments/components/deploy_board_wrapper.vue', () => { + let wrapper; + let mockApollo; + + const findDeployBoard = () => wrapper.findComponent(DeployBoard); + + const createWrapper = ({ propsData = {} } = {}) => { + mockApollo = createMockApollo(); + return mountExtended(DeployBoardWrapper, { + propsData: { environment: resolvedEnvironment, rolloutStatus, ...propsData }, + provide: { helpPagePath: '/help' }, + stubs: { transition: stubTransition() }, + apolloProvider: mockApollo, + }); + }; + + const expandCollapsedSection = async () => { + const button = wrapper.findByRole('button', { name: __('Expand') }); + await button.trigger('click'); + + return button; + }; + + afterEach(() => { + wrapper?.destroy(); + }); + + it('is labeled Kubernetes Pods', () => { + wrapper = createWrapper(); + + expect(wrapper.findByText(s__('DeployBoard|Kubernetes Pods')).exists()).toBe(true); + }); + + describe('collapse', () => { + let icon; + let collapse; + + beforeEach(() => { + wrapper = createWrapper(); + collapse = wrapper.findComponent(GlCollapse); + icon = wrapper.findComponent(GlIcon); + }); + + it('is collapsed by default', () => { + expect(collapse.attributes('visible')).toBeUndefined(); + expect(icon.props('name')).toBe('angle-right'); + }); + + it('opens on click', async () => { + const button = await expandCollapsedSection(); + + expect(button.attributes('aria-label')).toBe(__('Collapse')); + expect(collapse.attributes('visible')).toBe('visible'); + expect(icon.props('name')).toBe('angle-down'); + + const deployBoard = findDeployBoard(); + expect(deployBoard.exists()).toBe(true); + }); + }); + + describe('deploy board', () => { + it('passes the rollout status on and sets graphql to true', async () => { + wrapper = createWrapper(); + await expandCollapsedSection(); + + const deployBoard = findDeployBoard(); + expect(deployBoard.props('deployBoardData')).toEqual(rolloutStatus); + expect(deployBoard.props('graphql')).toBe(true); + }); + + it('sets the update to the canary via graphql', () => { + wrapper = createWrapper(); + jest.spyOn(mockApollo.defaultClient, 'mutate'); + const deployBoard = findDeployBoard(); + deployBoard.vm.$emit('changeCanaryWeight', 15); + expect(mockApollo.defaultClient.mutate).toHaveBeenCalledWith({ + mutation: setEnvironmentToChangeCanaryMutation, + variables: { environment: resolvedEnvironment, weight: 15 }, + }); + }); + + describe('is loading', () => { + it('should set the loading prop', async () => { + wrapper = createWrapper({ + propsData: { rolloutStatus: { ...rolloutStatus, status: 'loading' } }, + }); + + await expandCollapsedSection(); + + const deployBoard = findDeployBoard(); + + expect(deployBoard.props('isLoading')).toBe(true); + }); + }); + + describe('is empty', () => { + it('should set the empty prop', async () => { + wrapper = createWrapper({ + propsData: { rolloutStatus: { ...rolloutStatus, status: 'not_found' } }, + }); + + await expandCollapsedSection(); + + const deployBoard = findDeployBoard(); + + expect(deployBoard.props('isEmpty')).toBe(true); + }); + }); + }); +}); diff --git a/spec/frontend/environments/graphql/mock_data.js b/spec/frontend/environments/graphql/mock_data.js index fce30973547..1b7b35702de 100644 --- a/spec/frontend/environments/graphql/mock_data.js +++ b/spec/frontend/environments/graphql/mock_data.js @@ -1,3 +1,69 @@ +export const rolloutStatus = { + instances: [ + { + status: 'succeeded', + tooltip: 'tanuki-2334 Finished', + podName: 'production-tanuki-1', + stable: false, + }, + { + status: 'succeeded', + tooltip: 'tanuki-2335 Finished', + podName: 'production-tanuki-1', + stable: false, + }, + { + status: 'succeeded', + tooltip: 'tanuki-2336 Finished', + podName: 'production-tanuki-1', + stable: false, + }, + { + status: 'succeeded', + tooltip: 'tanuki-2337 Finished', + podName: 'production-tanuki-1', + stable: false, + }, + { + status: 'succeeded', + tooltip: 'tanuki-2338 Finished', + podName: 'production-tanuki-1', + stable: false, + }, + { + status: 'succeeded', + tooltip: 'tanuki-2339 Finished', + podName: 'production-tanuki-1', + stable: false, + }, + { status: 'succeeded', tooltip: 'tanuki-2340 Finished', podName: 'production-tanuki-1' }, + { status: 'succeeded', tooltip: 'tanuki-2334 Finished', podName: 'production-tanuki-1' }, + { status: 'succeeded', tooltip: 'tanuki-2335 Finished', podName: 'production-tanuki-1' }, + { status: 'succeeded', tooltip: 'tanuki-2336 Finished', podName: 'production-tanuki-1' }, + { status: 'succeeded', tooltip: 'tanuki-2337 Finished', podName: 'production-tanuki-1' }, + { status: 'succeeded', tooltip: 'tanuki-2338 Finished', podName: 'production-tanuki-1' }, + { status: 'succeeded', tooltip: 'tanuki-2339 Finished', podName: 'production-tanuki-1' }, + { status: 'succeeded', tooltip: 'tanuki-2340 Finished', podName: 'production-tanuki-1' }, + { status: 'running', tooltip: 'tanuki-2341 Deploying', podName: 'production-tanuki-1' }, + { status: 'running', tooltip: 'tanuki-2342 Deploying', podName: 'production-tanuki-1' }, + { status: 'running', tooltip: 'tanuki-2343 Deploying', podName: 'production-tanuki-1' }, + { status: 'failed', tooltip: 'tanuki-2344 Failed', podName: 'production-tanuki-1' }, + { status: 'unknown', tooltip: 'tanuki-2345 Ready', podName: 'production-tanuki-1' }, + { status: 'unknown', tooltip: 'tanuki-2346 Ready', podName: 'production-tanuki-1' }, + { status: 'pending', tooltip: 'tanuki-2348 Preparing', podName: 'production-tanuki-1' }, + { status: 'pending', tooltip: 'tanuki-2349 Preparing', podName: 'production-tanuki-1' }, + { status: 'pending', tooltip: 'tanuki-2350 Preparing', podName: 'production-tanuki-1' }, + { status: 'pending', tooltip: 'tanuki-2353 Preparing', podName: 'production-tanuki-1' }, + { status: 'pending', tooltip: 'tanuki-2354 waiting', podName: 'production-tanuki-1' }, + { status: 'pending', tooltip: 'tanuki-2355 waiting', podName: 'production-tanuki-1' }, + { status: 'pending', tooltip: 'tanuki-2356 waiting', podName: 'production-tanuki-1' }, + ], + abortUrl: 'url', + rollbackUrl: 'url', + completion: 100, + status: 'found', + canaryIngress: { canaryWeight: 50 }, +}; export const environmentsApp = { environments: [ { diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js index 0d5d216ba02..db596688dad 100644 --- a/spec/frontend/environments/new_environment_item_spec.js +++ b/spec/frontend/environments/new_environment_item_spec.js @@ -8,7 +8,8 @@ import { formatDate, getTimeago } from '~/lib/utils/datetime_utility'; import { __, s__, sprintf } from '~/locale'; import EnvironmentItem from '~/environments/components/new_environment_item.vue'; import Deployment from '~/environments/components/deployment.vue'; -import { resolvedEnvironment } from './graphql/mock_data'; +import DeployBoardWrapper from '~/environments/components/deploy_board_wrapper.vue'; +import { resolvedEnvironment, rolloutStatus } from './graphql/mock_data'; Vue.use(VueApollo); @@ -455,4 +456,35 @@ describe('~/environments/components/new_environment_item.vue', () => { expect(emptyState.exists()).toBe(false); }); }); + + describe('deploy boards', () => { + it('should show a deploy board if the environment has a rollout status', async () => { + const environment = { + ...resolvedEnvironment, + rolloutStatus, + }; + + wrapper = createWrapper({ + propsData: { environment }, + apolloProvider: createApolloProvider(), + }); + + await expandCollapsedSection(); + + const deployBoard = wrapper.findComponent(DeployBoardWrapper); + expect(deployBoard.exists()).toBe(true); + expect(deployBoard.props('rolloutStatus')).toBe(rolloutStatus); + }); + + it('should not show a deploy board if the environment has no rollout status', async () => { + wrapper = createWrapper({ + apolloProvider: createApolloProvider(), + }); + + await expandCollapsedSection(); + + const deployBoard = wrapper.findComponent(DeployBoardWrapper); + expect(deployBoard.exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/environments/new_environments_app_spec.js b/spec/frontend/environments/new_environments_app_spec.js index be86a202499..42e3608109b 100644 --- a/spec/frontend/environments/new_environments_app_spec.js +++ b/spec/frontend/environments/new_environments_app_spec.js @@ -10,6 +10,7 @@ import EnvironmentsApp from '~/environments/components/new_environments_app.vue' import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue'; import EnvironmentsItem from '~/environments/components/new_environment_item.vue'; import StopEnvironmentModal from '~/environments/components/stop_environment_modal.vue'; +import CanaryUpdateModal from '~/environments/components/canary_update_modal.vue'; import { resolvedEnvironmentsApp, resolvedFolder, resolvedEnvironment } from './graphql/mock_data'; Vue.use(VueApollo); @@ -20,6 +21,8 @@ describe('~/environments/components/new_environments_app.vue', () => { let environmentFolderMock; let paginationMock; let environmentToStopMock; + let environmentToChangeCanaryMock; + let weightMock; const createApolloProvider = () => { const mockResolvers = { @@ -30,6 +33,8 @@ describe('~/environments/components/new_environments_app.vue', () => { environmentToStop: environmentToStopMock, environmentToDelete: jest.fn().mockResolvedValue(resolvedEnvironment), environmentToRollback: jest.fn().mockResolvedValue(resolvedEnvironment), + environmentToChangeCanary: environmentToChangeCanaryMock, + weight: weightMock, }, }; @@ -53,6 +58,8 @@ describe('~/environments/components/new_environments_app.vue', () => { environmentsApp, folder, environmentToStop = {}, + environmentToChangeCanary = {}, + weight = 0, pageInfo = { total: 20, perPage: 5, @@ -67,6 +74,8 @@ describe('~/environments/components/new_environments_app.vue', () => { environmentFolderMock.mockReturnValue(folder); paginationMock.mockReturnValue(pageInfo); environmentToStopMock.mockReturnValue(environmentToStop); + environmentToChangeCanaryMock.mockReturnValue(environmentToChangeCanary); + weightMock.mockReturnValue(weight); const apolloProvider = createApolloProvider(); wrapper = createWrapper({ apolloProvider, provide }); @@ -78,6 +87,8 @@ describe('~/environments/components/new_environments_app.vue', () => { environmentAppMock = jest.fn(); environmentFolderMock = jest.fn(); environmentToStopMock = jest.fn(); + environmentToChangeCanaryMock = jest.fn(); + weightMock = jest.fn(); paginationMock = jest.fn(); }); @@ -207,6 +218,19 @@ describe('~/environments/components/new_environments_app.vue', () => { expect(modal.props('environment')).toMatchObject(resolvedEnvironment); }); + + it('should pass the environment to change canary to the canary update modal', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + environmentToChangeCanary: resolvedEnvironment, + weight: 10, + }); + + const modal = wrapper.findComponent(CanaryUpdateModal); + + expect(modal.props('environment')).toMatchObject(resolvedEnvironment); + }); }); describe('pagination', () => { diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 481cae6031b..1728d4fc3f3 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -425,7 +425,7 @@ RSpec.describe Namespace do end context 'traversal_ids on create' do - context 'default traversal_ids' do + shared_examples 'default traversal_ids' do let(:namespace) { build(:namespace) } before do @@ -435,6 +435,18 @@ RSpec.describe Namespace do it { expect(namespace.traversal_ids).to eq [namespace.id] } end + + context 'with before_commit callback' do + it_behaves_like 'default traversal_ids' + end + + context 'with after_create callback' do + before do + stub_feature_flags(sync_traversal_ids_before_commit: false) + end + + it_behaves_like 'default traversal_ids' + end end describe "after_commit :expire_child_caches" do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f24e5a42e95..c487c87db1c 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -236,27 +236,42 @@ RSpec.describe Project, factory_default: :keep do end context 'with project namespaces' do - it 'automatically creates a project namespace' do - project = build(:project, path: 'hopefully-valid-path1') - project.save! + shared_examples 'creates project namespace' do + it 'automatically creates a project namespace' do + project = build(:project, path: 'hopefully-valid-path1') + project.save! - expect(project).to be_persisted - expect(project.project_namespace).to be_persisted - expect(project.project_namespace).to be_in_sync_with_project(project) - end + expect(project).to be_persisted + expect(project.project_namespace).to be_persisted + expect(project.project_namespace).to be_in_sync_with_project(project) + expect(project.reload.project_namespace.traversal_ids).to eq([project.namespace.traversal_ids, project.project_namespace.id].flatten.compact) + end - context 'with FF disabled' do - before do - stub_feature_flags(create_project_namespace_on_project_create: false) + context 'with FF disabled' do + before do + stub_feature_flags(create_project_namespace_on_project_create: false) + end + + it 'does not create a project namespace' do + project = build(:project, path: 'hopefully-valid-path2') + project.save! + + expect(project).to be_persisted + expect(project.project_namespace).to be_nil + end end + end - it 'does not create a project namespace' do - project = build(:project, path: 'hopefully-valid-path2') - project.save! + context 'sync-ing traversal_ids in before_commit callback' do + it_behaves_like 'creates project namespace' + end - expect(project).to be_persisted - expect(project.project_namespace).to be_nil + context 'sync-ing traversal_ids in after_create callback' do + before do + stub_feature_flags(sync_traversal_ids_before_commit: false) end + + it_behaves_like 'creates project namespace' end end end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 21a8622e08d..f42fc7aabc2 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -561,17 +561,6 @@ RSpec.describe API::Repositories do let(:request) { get api(route, guest) } end end - - context 'api_caching_rate_limit_repository_compare is disabled' do - before do - stub_feature_flags(api_caching_rate_limit_repository_compare: false) - end - - it_behaves_like 'repository compare' do - let(:project) { create(:project, :public, :repository) } - let(:current_user) { nil } - end - end end describe 'GET /projects/:id/repository/contributors' do diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb index 1a3dde78580..7ec523a1f2b 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -8,6 +8,10 @@ RSpec.describe Groups::CreateService, '#execute' do subject { service.execute } + shared_examples 'has sync-ed traversal_ids' do + specify { expect(subject.reload.traversal_ids).to eq([subject.parent&.traversal_ids, subject.id].flatten.compact) } + end + describe 'visibility level restrictions' do let!(:service) { described_class.new(user, group_params) } @@ -77,6 +81,18 @@ RSpec.describe Groups::CreateService, '#execute' do it 'adds an onboarding progress record' do expect { subject }.to change(OnboardingProgress, :count).from(0).to(1) end + + context 'with before_commit callback' do + it_behaves_like 'has sync-ed traversal_ids' + end + + context 'with after_create callback' do + before do + stub_feature_flags(sync_traversal_ids_before_commit: false) + end + + it_behaves_like 'has sync-ed traversal_ids' + end end context 'when user can not create a group' do @@ -102,6 +118,18 @@ RSpec.describe Groups::CreateService, '#execute' do it 'does not add an onboarding progress record' do expect { subject }.not_to change(OnboardingProgress, :count).from(0) end + + context 'with before_commit callback' do + it_behaves_like 'has sync-ed traversal_ids' + end + + context 'with after_create callback' do + before do + stub_feature_flags(sync_traversal_ids_before_commit: false) + end + + it_behaves_like 'has sync-ed traversal_ids' + end end context 'as guest' do diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index 90bab5bf76a..f4bb1f0877b 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -97,8 +97,6 @@ RSpec.describe Issues::CreateService do let(:issue_after) { create(:issue, project: project, relative_position: 50) } before do - project.add_reporter(user) - opts.merge!(move_between_ids: [issue_before.id, issue_after.id]) end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 43a9d69f3bb..10f694827e1 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -190,9 +190,13 @@ RSpec.describe Projects::CreateService, '#execute' do user.refresh_authorized_projects # Ensure cache is warm end - it do - project = create_project(user, opts.merge!(namespace_id: group.id)) + subject(:project) { create_project(user, opts.merge!(namespace_id: group.id)) } + shared_examples 'has sync-ed traversal_ids' do + specify { expect(project.reload.project_namespace.traversal_ids).to eq([project.namespace.traversal_ids, project.project_namespace.id].flatten.compact) } + end + + it 'creates the project' do expect(project).to be_valid expect(project.owner).to eq(group) expect(project.namespace).to eq(group) @@ -200,6 +204,18 @@ RSpec.describe Projects::CreateService, '#execute' do expect(user.authorized_projects).to include(project) expect(project.project_namespace).to be_in_sync_with_project(project) end + + context 'with before_commit callback' do + it_behaves_like 'has sync-ed traversal_ids' + end + + context 'with after_create callback' do + before do + stub_feature_flags(sync_traversal_ids_before_commit: false) + end + + it_behaves_like 'has sync-ed traversal_ids' + end end context 'group sharing', :sidekiq_inline do |