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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-02-16 15:13:53 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-02-16 15:13:53 +0300
commitabed3501da6ecd7ae31cfe2b8fa7654e91a26fb6 (patch)
tree65318870e1bff62cd176e7fe5bd26155cc38c08f /spec
parent6259da15b5ede93a9f688ddd062860166e7cf21a (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/boards/new_issue_spec.rb19
-rw-r--r--spec/features/issues/user_interacts_with_awards_spec.rb5
-rw-r--r--spec/frontend/boards/components/board_new_issue_spec.js35
-rw-r--r--spec/frontend/environments/canary_ingress_spec.js27
-rw-r--r--spec/frontend/environments/deploy_board_component_spec.js65
-rw-r--r--spec/frontend/environments/deploy_board_wrapper_spec.js124
-rw-r--r--spec/frontend/environments/graphql/mock_data.js66
-rw-r--r--spec/frontend/environments/new_environment_item_spec.js34
-rw-r--r--spec/frontend/environments/new_environments_app_spec.js24
-rw-r--r--spec/models/namespace_spec.rb14
-rw-r--r--spec/models/project_spec.rb45
-rw-r--r--spec/requests/api/repositories_spec.rb11
-rw-r--r--spec/services/groups/create_service_spec.rb28
-rw-r--r--spec/services/issues/create_service_spec.rb2
-rw-r--r--spec/services/projects/create_service_spec.rb20
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