diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-18 18:10:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-18 18:10:42 +0300 |
commit | bfc7eec58ee891178d2a81c424f6c1de22feae5f (patch) | |
tree | 7cf9e2f76befc383e99d28d397ecec4bb9e8d2c9 /spec | |
parent | f23a9a17ed6237c346d2e9210c6841e319e8d030 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
28 files changed, 613 insertions, 172 deletions
diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb index 5ac4a5c1840..5430329d47d 100644 --- a/spec/features/security/dashboard_access_spec.rb +++ b/spec/features/security/dashboard_access_spec.rb @@ -57,7 +57,7 @@ RSpec.describe "Dashboard access" do it { expect(new_group_path).to be_denied_for :visitor } end - describe "GET /profile/groups" do + describe "GET /dashboard/groups" do subject { dashboard_groups_path } it { is_expected.to be_allowed_for :admin } diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb index 3aa8278866c..301efd2d99b 100644 --- a/spec/features/security/profile_access_spec.rb +++ b/spec/features/security/profile_access_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe "Profile access" do include AccessMatchers - describe "GET /profile/keys" do + describe "GET /-/profile/keys" do subject { profile_keys_path } it { is_expected.to be_allowed_for :admin } @@ -13,7 +13,7 @@ RSpec.describe "Profile access" do it { is_expected.to be_denied_for :visitor } end - describe "GET /profile" do + describe "GET /-/profile" do subject { profile_path } it { is_expected.to be_allowed_for :admin } @@ -21,7 +21,7 @@ RSpec.describe "Profile access" do it { is_expected.to be_denied_for :visitor } end - describe "GET /profile/account" do + describe "GET /-/profile/account" do subject { profile_account_path } it { is_expected.to be_allowed_for :admin } @@ -29,7 +29,7 @@ RSpec.describe "Profile access" do it { is_expected.to be_denied_for :visitor } end - describe "GET /profile/preferences" do + describe "GET /-/profile/preferences" do subject { profile_preferences_path } it { is_expected.to be_allowed_for :admin } @@ -37,7 +37,7 @@ RSpec.describe "Profile access" do it { is_expected.to be_denied_for :visitor } end - describe "GET /profile/audit_log" do + describe "GET /-/profile/audit_log" do subject { audit_log_profile_path } it { is_expected.to be_allowed_for :admin } @@ -45,7 +45,7 @@ RSpec.describe "Profile access" do it { is_expected.to be_denied_for :visitor } end - describe "GET /profile/notifications" do + describe "GET /-/profile/notifications" do subject { profile_notifications_path } it { is_expected.to be_allowed_for :admin } diff --git a/spec/frontend/boards/components/board_card_layout_spec.js b/spec/frontend/boards/components/board_card_layout_spec.js index d627f971724..d8633871e8d 100644 --- a/spec/frontend/boards/components/board_card_layout_spec.js +++ b/spec/frontend/boards/components/board_card_layout_spec.js @@ -1,7 +1,8 @@ /* global List */ /* global ListLabel */ -import { shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import waitForPromises from 'helpers/wait_for_promises'; @@ -10,20 +11,35 @@ import axios from '~/lib/utils/axios_utils'; import '~/boards/models/label'; import '~/boards/models/assignee'; import '~/boards/models/list'; -import store from '~/boards/stores'; +import boardsVuexStore from '~/boards/stores'; import boardsStore from '~/boards/stores/boards_store'; import BoardCardLayout from '~/boards/components/board_card_layout.vue'; import issueCardInner from '~/boards/components/issue_card_inner.vue'; import { listObj, boardsMockInterceptor, setMockEndpoints } from '../mock_data'; +import { ISSUABLE } from '~/boards/constants'; + describe('Board card layout', () => { let wrapper; let mock; let list; + let store; + + const localVue = createLocalVue(); + localVue.use(Vuex); + + const createStore = ({ getters = {}, actions = {} } = {}) => { + store = new Vuex.Store({ + ...boardsVuexStore, + actions, + getters, + }); + }; // this particular mount component needs to be used after the root beforeEach because it depends on list being initialized - const mountComponent = (propsData) => { + const mountComponent = ({ propsData = {}, provide = {} } = {}) => { wrapper = shallowMount(BoardCardLayout, { + localVue, stubs: { issueCardInner, }, @@ -39,6 +55,7 @@ describe('Board card layout', () => { groupId: null, rootPath: '/', scopedLabelsAvailable: false, + ...provide, }, }); }; @@ -75,6 +92,7 @@ describe('Board card layout', () => { describe('mouse events', () => { it('sets showDetail to true on mousedown', async () => { + createStore(); mountComponent(); wrapper.trigger('mousedown'); @@ -84,6 +102,7 @@ describe('Board card layout', () => { }); it('sets showDetail to false on mousemove', async () => { + createStore(); mountComponent(); wrapper.trigger('mousedown'); await wrapper.vm.$nextTick(); @@ -92,5 +111,49 @@ describe('Board card layout', () => { await wrapper.vm.$nextTick(); expect(wrapper.vm.showDetail).toBe(false); }); + + it("calls 'setActiveId' when 'graphqlBoardLists' feature flag is turned on", async () => { + const setActiveId = jest.fn(); + createStore({ + actions: { + setActiveId, + }, + }); + mountComponent({ + provide: { + glFeatures: { graphqlBoardLists: true }, + }, + }); + + wrapper.trigger('mouseup'); + await wrapper.vm.$nextTick(); + + expect(setActiveId).toHaveBeenCalledTimes(1); + expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), { + id: list.issues[0].id, + sidebarType: ISSUABLE, + }); + }); + + it("calls 'setActiveId' when epic swimlanes is active", async () => { + const setActiveId = jest.fn(); + const isSwimlanesOn = () => true; + createStore({ + getters: { isSwimlanesOn }, + actions: { + setActiveId, + }, + }); + mountComponent(); + + wrapper.trigger('mouseup'); + await wrapper.vm.$nextTick(); + + expect(setActiveId).toHaveBeenCalledTimes(1); + expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), { + id: list.issues[0].id, + sidebarType: ISSUABLE, + }); + }); }); }); diff --git a/spec/frontend/environments/environment_table_spec.js b/spec/frontend/environments/environment_table_spec.js index c9dc29af515..daef35bcf99 100644 --- a/spec/frontend/environments/environment_table_spec.js +++ b/spec/frontend/environments/environment_table_spec.js @@ -6,8 +6,6 @@ import CanaryUpdateModal from '~/environments/components/canary_update_modal.vue import { folder, deployBoardMockData } from './mock_data'; const eeOnlyProps = { - canaryDeploymentFeatureId: 'canary_deployment', - showCanaryDeploymentCallout: true, userCalloutsPath: '/callouts', lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', helpCanaryDeploymentsPath: 'help/canary-deployments', @@ -53,8 +51,6 @@ describe('Environment table', () => { propsData: { environments: [mockItem], canReadEnvironment: true, - canaryDeploymentFeatureId: 'canary_deployment', - showCanaryDeploymentCallout: true, userCalloutsPath: '/callouts', lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', helpCanaryDeploymentsPath: 'help/canary-deployments', @@ -83,8 +79,6 @@ describe('Environment table', () => { environments: [mockItem], canCreateDeployment: false, canReadEnvironment: true, - canaryDeploymentFeatureId: 'canary_deployment', - showCanaryDeploymentCallout: true, userCalloutsPath: '/callouts', lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', helpCanaryDeploymentsPath: 'help/canary-deployments', @@ -122,8 +116,6 @@ describe('Environment table', () => { propsData: { environments: [mockItem], canReadEnvironment: true, - canaryDeploymentFeatureId: 'canary_deployment', - showCanaryDeploymentCallout: true, userCalloutsPath: '/callouts', lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', helpCanaryDeploymentsPath: 'help/canary-deployments', @@ -152,8 +144,6 @@ describe('Environment table', () => { environments: [mockItem], canCreateDeployment: false, canReadEnvironment: true, - canaryDeploymentFeatureId: 'canary_deployment', - showCanaryDeploymentCallout: true, userCalloutsPath: '/callouts', lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', helpCanaryDeploymentsPath: 'help/canary-deployments', @@ -169,32 +159,6 @@ describe('Environment table', () => { }); }); - it('should render canary callout', async () => { - const mockItem = { - name: 'review', - folderName: 'review', - size: 3, - isFolder: true, - environment_path: 'url', - showCanaryCallout: true, - }; - - await factory({ - propsData: { - environments: [mockItem], - canCreateDeployment: false, - canReadEnvironment: true, - canaryDeploymentFeatureId: 'canary_deployment', - showCanaryDeploymentCallout: true, - userCalloutsPath: '/callouts', - lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', - helpCanaryDeploymentsPath: 'help/canary-deployments', - }, - }); - - expect(wrapper.find('.canary-deployment-callout').exists()).toBe(true); - }); - describe('sortEnvironments', () => { it('should sort environments by last updated', () => { const mockItems = [ diff --git a/spec/frontend/environments/environments_app_spec.js b/spec/frontend/environments/environments_app_spec.js index 7d61e99ec63..d6614e2fd2b 100644 --- a/spec/frontend/environments/environments_app_spec.js +++ b/spec/frontend/environments/environments_app_spec.js @@ -6,7 +6,6 @@ import Container from '~/environments/components/container.vue'; import EmptyState from '~/environments/components/empty_state.vue'; import EnvironmentsApp from '~/environments/components/environments_app.vue'; import DeployBoard from '~/environments/components/deploy_board.vue'; -import CanaryDeploymentBoard from '~/environments/components/canary_deployment_callout.vue'; import axios from '~/lib/utils/axios_utils'; import { environment, folder } from './mock_data'; @@ -20,8 +19,6 @@ describe('Environment', () => { canReadEnvironment: true, newEnvironmentPath: 'environments/new', helpPagePath: 'help', - canaryDeploymentFeatureId: 'canary_deployment', - showCanaryDeploymentCallout: true, userCalloutsPath: '/callouts', lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', helpCanaryDeploymentsPath: 'help/canary-deployments', @@ -38,9 +35,6 @@ describe('Environment', () => { }); }; - const canaryPromoKeyValue = () => - wrapper.find(CanaryDeploymentBoard).attributes('data-js-canary-promo-key'); - const createWrapper = (shallow = false, props = {}) => { const fn = shallow ? shallowMount : mount; wrapper = extendedWrapper(fn(EnvironmentsApp, { propsData: { ...mockData, ...props } })); @@ -148,28 +142,6 @@ describe('Environment', () => { ).toBe(true); }); }); - - describe('canary callout with one environment', () => { - it('should render banner underneath first environment', () => { - expect(canaryPromoKeyValue()).toBe('0'); - }); - }); - - describe('canary callout with multiple environments', () => { - beforeEach(() => { - mockRequest(200, { - environments: [environment, environment], - stopped_count: 1, - available_count: 0, - }); - - return createWrapper(); - }); - - it('should render banner underneath second environment', () => { - expect(canaryPromoKeyValue()).toBe('1'); - }); - }); }); }); diff --git a/spec/frontend/environments/environments_folder_view_spec.js b/spec/frontend/environments/environments_folder_view_spec.js index 43f809dda88..e4661d27872 100644 --- a/spec/frontend/environments/environments_folder_view_spec.js +++ b/spec/frontend/environments/environments_folder_view_spec.js @@ -13,8 +13,6 @@ describe('Environments Folder View', () => { folderName: 'review', canReadEnvironment: true, cssContainerClass: 'container', - canaryDeploymentFeatureId: 'canary_deployment', - showCanaryDeploymentCallout: true, userCalloutsPath: '/callouts', lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', helpCanaryDeploymentsPath: 'help/canary-deployments', diff --git a/spec/frontend/environments/environments_store_spec.js b/spec/frontend/environments/environments_store_spec.js index 60f0152bd47..4a07281353f 100644 --- a/spec/frontend/environments/environments_store_spec.js +++ b/spec/frontend/environments/environments_store_spec.js @@ -214,18 +214,4 @@ describe('Store', () => { expect(store.state.environments[0].deployBoardData).toEqual(deployBoardMockData); }); }); - - describe('canaryCallout', () => { - it('should add banner underneath the second environment', () => { - store.storeEnvironments(serverData); - - expect(store.state.environments[1].showCanaryCallout).toEqual(true); - }); - - it('should add banner underneath first environment when only one environment', () => { - store.storeEnvironments(serverData.slice(0, 1)); - - expect(store.state.environments[0].showCanaryCallout).toEqual(true); - }); - }); }); diff --git a/spec/frontend/environments/folder/environments_folder_view_spec.js b/spec/frontend/environments/folder/environments_folder_view_spec.js index abead001832..3943e89c6cf 100644 --- a/spec/frontend/environments/folder/environments_folder_view_spec.js +++ b/spec/frontend/environments/folder/environments_folder_view_spec.js @@ -16,8 +16,6 @@ describe('Environments Folder View', () => { folderName: 'review', canReadEnvironment: true, cssContainerClass: 'container', - canaryDeploymentFeatureId: 'canary_deployment', - showCanaryDeploymentCallout: true, userCalloutsPath: '/callouts', lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', helpCanaryDeploymentsPath: 'help/canary-deployments', diff --git a/spec/frontend/jira_connect/api_spec.js b/spec/frontend/jira_connect/api_spec.js index e5a2484c827..8fecbee9ca7 100644 --- a/spec/frontend/jira_connect/api_spec.js +++ b/spec/frontend/jira_connect/api_spec.js @@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import httpStatus from '~/lib/utils/http_status'; -import { addSubscription, removeSubscription } from '~/jira_connect/api'; +import { addSubscription, removeSubscription, fetchGroups } from '~/jira_connect/api'; describe('JiraConnect API', () => { let mock; @@ -72,4 +72,36 @@ describe('JiraConnect API', () => { expect(response.data).toEqual(mockResponse); }); }); + + describe('fetchGroups', () => { + const mockGroupsPath = 'groupsPath'; + const mockPage = 1; + const mockPerPage = 10; + + const makeRequest = () => + fetchGroups(mockGroupsPath, { + page: mockPage, + perPage: mockPerPage, + }); + + it('returns success response', async () => { + jest.spyOn(axios, 'get'); + mock + .onGet(mockGroupsPath, { + page: mockPage, + per_page: mockPerPage, + }) + .replyOnce(httpStatus.OK, mockResponse); + + response = await makeRequest(); + + expect(axios.get).toHaveBeenCalledWith(mockGroupsPath, { + params: { + page: mockPage, + per_page: mockPerPage, + }, + }); + expect(response.data).toEqual(mockResponse); + }); + }); }); diff --git a/spec/frontend/jira_connect/components/groups_list_item_spec.js b/spec/frontend/jira_connect/components/groups_list_item_spec.js new file mode 100644 index 00000000000..77577c53cf4 --- /dev/null +++ b/spec/frontend/jira_connect/components/groups_list_item_spec.js @@ -0,0 +1,46 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlAvatar } from '@gitlab/ui'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { mockGroup1 } from '../mock_data'; + +import GroupsListItem from '~/jira_connect/components/groups_list_item.vue'; + +describe('GroupsListItem', () => { + let wrapper; + + const createComponent = () => { + wrapper = extendedWrapper( + shallowMount(GroupsListItem, { + propsData: { + group: mockGroup1, + }, + }), + ); + }; + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findGlAvatar = () => wrapper.find(GlAvatar); + const findGroupName = () => wrapper.findByTestId('group-list-item-name'); + const findGroupDescription = () => wrapper.findByTestId('group-list-item-description'); + + it('renders group avatar', () => { + expect(findGlAvatar().exists()).toBe(true); + expect(findGlAvatar().props('src')).toBe(mockGroup1.avatar_url); + }); + + it('renders group name', () => { + expect(findGroupName().text()).toBe(mockGroup1.full_name); + }); + + it('renders group description', () => { + expect(findGroupDescription().text()).toBe(mockGroup1.description); + }); +}); diff --git a/spec/frontend/jira_connect/components/groups_list_spec.js b/spec/frontend/jira_connect/components/groups_list_spec.js new file mode 100644 index 00000000000..94f158e6344 --- /dev/null +++ b/spec/frontend/jira_connect/components/groups_list_spec.js @@ -0,0 +1,71 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlLoadingIcon } from '@gitlab/ui'; +import waitForPromises from 'helpers/wait_for_promises'; + +import { fetchGroups } from '~/jira_connect/api'; +import GroupsList from '~/jira_connect/components/groups_list.vue'; +import GroupsListItem from '~/jira_connect/components/groups_list_item.vue'; +import { mockGroup1, mockGroup2 } from '../mock_data'; + +jest.mock('~/jira_connect/api', () => { + return { + fetchGroups: jest.fn(), + }; +}); +describe('GroupsList', () => { + let wrapper; + + const mockEmptyResponse = { data: [] }; + + const createComponent = (options = {}) => { + wrapper = shallowMount(GroupsList, { + ...options, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findGlLoadingIcon = () => wrapper.find(GlLoadingIcon); + const findAllItems = () => wrapper.findAll(GroupsListItem); + const findFirstItem = () => findAllItems().at(0); + const findSecondItem = () => findAllItems().at(1); + + describe('isLoading is true', () => { + it('renders loading icon', async () => { + fetchGroups.mockResolvedValue(mockEmptyResponse); + createComponent(); + + wrapper.setData({ isLoading: true }); + await wrapper.vm.$nextTick(); + + expect(findGlLoadingIcon().exists()).toBe(true); + }); + }); + + describe('no groups returned', () => { + it('renders empty state', async () => { + fetchGroups.mockResolvedValue(mockEmptyResponse); + createComponent(); + + await waitForPromises(); + + expect(wrapper.text()).toContain('No available namespaces'); + }); + }); + + describe('with groups returned', () => { + it('renders groups list', async () => { + fetchGroups.mockResolvedValue({ data: [mockGroup1, mockGroup2] }); + createComponent(); + + await waitForPromises(); + + expect(findAllItems().length).toBe(2); + expect(findFirstItem().props('group')).toBe(mockGroup1); + expect(findSecondItem().props('group')).toBe(mockGroup2); + }); + }); +}); diff --git a/spec/frontend/jira_connect/mock_data.js b/spec/frontend/jira_connect/mock_data.js new file mode 100644 index 00000000000..31565912489 --- /dev/null +++ b/spec/frontend/jira_connect/mock_data.js @@ -0,0 +1,15 @@ +export const mockGroup1 = { + id: 1, + avatar_url: 'avatar.png', + name: 'Gitlab Org', + full_name: 'Gitlab Org', + description: 'Open source software to collaborate on code', +}; + +export const mockGroup2 = { + id: 2, + avatar_url: 'avatar.png', + name: 'Gitlab Com', + full_name: 'Gitlab Com', + description: 'For GitLab company related projects', +}; diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js index 3f0d73cbb10..66efd43262b 100644 --- a/spec/frontend/lib/utils/datetime_utility_spec.js +++ b/spec/frontend/lib/utils/datetime_utility_spec.js @@ -842,3 +842,58 @@ describe('format24HourTimeStringFromInt', () => { }); }); }); + +describe('getOverlappingDaysInPeriods', () => { + const start = new Date(2021, 0, 11); + const end = new Date(2021, 0, 13); + + describe('when date periods overlap', () => { + const givenPeriodLeft = new Date(2021, 0, 11); + const givenPeriodRight = new Date(2021, 0, 14); + + it('returns an overlap object that contains the amount of days overlapping, start date of overlap and end date of overlap', () => { + expect( + datetimeUtility.getOverlappingDaysInPeriods( + { start, end }, + { start: givenPeriodLeft, end: givenPeriodRight }, + ), + ).toEqual({ + daysOverlap: 2, + overlapStartDate: givenPeriodLeft.getTime(), + overlapEndDate: end.getTime(), + }); + }); + }); + + describe('when date periods do not overlap', () => { + const givenPeriodLeft = new Date(2021, 0, 9); + const givenPeriodRight = new Date(2021, 0, 10); + + it('returns an overlap object that contains a 0 value for days overlapping', () => { + expect( + datetimeUtility.getOverlappingDaysInPeriods( + { start, end }, + { start: givenPeriodLeft, end: givenPeriodRight }, + ), + ).toEqual({ daysOverlap: 0 }); + }); + }); + + describe('when date periods contain an invalid Date', () => { + const startInvalid = new Date(NaN); + const endInvalid = new Date(NaN); + const error = __('Invalid period'); + + it('throws an exception when the left period contains an invalid date', () => { + expect(() => + datetimeUtility.getOverlappingDaysInPeriods({ start, end }, { start: startInvalid, end }), + ).toThrow(error); + }); + + it('throws an exception when the right period contains an invalid date', () => { + expect(() => + datetimeUtility.getOverlappingDaysInPeriods({ start, end }, { start, end: endInvalid }), + ).toThrow(error); + }); + }); +}); diff --git a/spec/graphql/types/notes/note_type_spec.rb b/spec/graphql/types/notes/note_type_spec.rb index 180d13d35d2..03ff7828cf5 100644 --- a/spec/graphql/types/notes/note_type_spec.rb +++ b/spec/graphql/types/notes/note_type_spec.rb @@ -22,6 +22,7 @@ RSpec.describe GitlabSchema.types['Note'] do system_note_icon_name updated_at user_permissions + url ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/helpers/jira_connect_helper_spec.rb b/spec/helpers/jira_connect_helper_spec.rb new file mode 100644 index 00000000000..a99072527c8 --- /dev/null +++ b/spec/helpers/jira_connect_helper_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe JiraConnectHelper do + describe '#jira_connect_app_data' do + subject { helper.jira_connect_app_data } + + it 'includes Jira Connect app attributes' do + is_expected.to include( + :groups_path + ) + end + end +end diff --git a/spec/lib/atlassian/jira_connect/client_spec.rb b/spec/lib/atlassian/jira_connect/client_spec.rb index e042b2b183d..21ee40f22fe 100644 --- a/spec/lib/atlassian/jira_connect/client_spec.rb +++ b/spec/lib/atlassian/jira_connect/client_spec.rb @@ -107,6 +107,75 @@ RSpec.describe Atlassian::JiraConnect::Client do } end + describe '#handle_response' do + let(:errors) { [{ 'message' => 'X' }, { 'message' => 'Y' }] } + let(:processed) { subject.send(:handle_response, response, 'foo') { |x| [:data, x] } } + + context 'the response is 200 OK' do + let(:response) { double(code: 200, parsed_response: :foo) } + + it 'yields to the block' do + expect(processed).to eq [:data, :foo] + end + end + + context 'the response is 400 bad request' do + let(:response) { double(code: 400, parsed_response: errors) } + + it 'extracts the errors messages' do + expect(processed).to eq('errorMessages' => %w(X Y)) + end + end + + context 'the response is 401 forbidden' do + let(:response) { double(code: 401, parsed_response: nil) } + + it 'reports that our JWT is wrong' do + expect(processed).to eq('errorMessages' => ['Invalid JWT']) + end + end + + context 'the response is 403' do + let(:response) { double(code: 403, parsed_response: nil) } + + it 'reports that the App is misconfigured' do + expect(processed).to eq('errorMessages' => ['App does not support foo']) + end + end + + context 'the response is 413' do + let(:response) { double(code: 413, parsed_response: errors) } + + it 'extracts the errors messages' do + expect(processed).to eq('errorMessages' => ['Data too large', 'X', 'Y']) + end + end + + context 'the response is 429' do + let(:response) { double(code: 429, parsed_response: nil) } + + it 'reports that we exceeded the rate limit' do + expect(processed).to eq('errorMessages' => ['Rate limit exceeded']) + end + end + + context 'the response is 503' do + let(:response) { double(code: 503, parsed_response: nil) } + + it 'reports that the service is unavailable' do + expect(processed).to eq('errorMessages' => ['Service unavailable']) + end + end + + context 'the response is anything else' do + let(:response) { double(code: 1000, parsed_response: :something) } + + it 'reports that this was unanticipated' do + expect(processed).to eq('errorMessages' => ['Unknown error'], 'response' => :something) + end + end + end + describe '#store_deploy_info' do let_it_be(:environment) { create(:environment, name: 'DEV', project: project) } let_it_be(:deployments) do @@ -126,10 +195,20 @@ RSpec.describe Atlassian::JiraConnect::Client do ->(text) { matcher.matches?(text) } end + let(:rejections) { [] } + let(:response_body) do + { + acceptedDeployments: [], + rejectedDeployments: rejections, + unknownIssueKeys: [] + }.to_json + end + before do path = '/rest/deployments/0.1/bulk' stub_full_request('https://gitlab-test.atlassian.net' + path, method: :post) .with(body: body, headers: expected_headers(path)) + .to_return(body: response_body, headers: { 'Content-Type': 'application/json' }) end it "calls the API with auth headers" do @@ -137,7 +216,7 @@ RSpec.describe Atlassian::JiraConnect::Client do end it 'only sends information about relevant MRs' do - expect(subject).to receive(:post).with('/rest/deployments/0.1/bulk', { deployments: have_attributes(size: 6) }) + expect(subject).to receive(:post).with('/rest/deployments/0.1/bulk', { deployments: have_attributes(size: 6) }).and_call_original subject.send(:store_deploy_info, project: project, deployments: deployments) end @@ -148,6 +227,18 @@ RSpec.describe Atlassian::JiraConnect::Client do subject.send(:store_deploy_info, project: project, deployments: deployments.take(1)) end + context 'there are errors' do + let(:rejections) do + [{ errors: [{ message: 'X' }, { message: 'Y' }] }, { errors: [{ message: 'Z' }] }] + end + + it 'reports the errors' do + response = subject.send(:store_deploy_info, project: project, deployments: deployments) + + expect(response['errorMessages']).to eq(%w(X Y Z)) + end + end + it 'does not call the API if the feature flag is not enabled' do stub_feature_flags(jira_sync_deployments: false) @@ -159,7 +250,7 @@ RSpec.describe Atlassian::JiraConnect::Client do it 'does call the API if the feature flag enabled for the project' do stub_feature_flags(jira_sync_deployments: project) - expect(subject).to receive(:post).with('/rest/deployments/0.1/bulk', { deployments: Array }) + expect(subject).to receive(:post).with('/rest/deployments/0.1/bulk', { deployments: Array }).and_call_original subject.send(:store_deploy_info, project: project, deployments: deployments) end @@ -178,12 +269,22 @@ RSpec.describe Atlassian::JiraConnect::Client do ->(text) { matcher.matches?(text) } end + let(:failures) { {} } + let(:response_body) do + { + acceptedFeatureFlags: [], + failedFeatureFlags: failures, + unknownIssueKeys: [] + }.to_json + end + before do feature_flags.first.update!(description: 'RELEVANT-123') feature_flags.second.update!(description: 'RELEVANT-123') path = '/rest/featureflags/0.1/bulk' stub_full_request('https://gitlab-test.atlassian.net' + path, method: :post) .with(body: body, headers: expected_headers(path)) + .to_return(body: response_body, headers: { 'Content-Type': 'application/json' }) end it "calls the API with auth headers" do @@ -193,7 +294,7 @@ RSpec.describe Atlassian::JiraConnect::Client do it 'only sends information about relevant MRs' do expect(subject).to receive(:post).with('/rest/featureflags/0.1/bulk', { flags: have_attributes(size: 2), properties: Hash - }) + }).and_call_original subject.send(:store_ff_info, project: project, feature_flags: feature_flags) end @@ -204,6 +305,21 @@ RSpec.describe Atlassian::JiraConnect::Client do subject.send(:store_ff_info, project: project, feature_flags: [feature_flags.last]) end + context 'there are errors' do + let(:failures) do + { + a: [{ message: 'X' }, { message: 'Y' }], + b: [{ message: 'Z' }] + } + end + + it 'reports the errors' do + response = subject.send(:store_ff_info, project: project, feature_flags: feature_flags) + + expect(response['errorMessages']).to eq(['a: X', 'a: Y', 'b: Z']) + end + end + it 'does not call the API if the feature flag is not enabled' do stub_feature_flags(jira_sync_feature_flags: false) @@ -217,7 +333,7 @@ RSpec.describe Atlassian::JiraConnect::Client do expect(subject).to receive(:post).with('/rest/featureflags/0.1/bulk', { flags: Array, properties: Hash - }) + }).and_call_original subject.send(:store_ff_info, project: project, feature_flags: feature_flags) end @@ -234,10 +350,20 @@ RSpec.describe Atlassian::JiraConnect::Client do ->(text) { matcher.matches?(text) } end + let(:failures) { [] } + let(:response_body) do + { + acceptedBuilds: [], + rejectedBuilds: failures, + unknownIssueKeys: [] + }.to_json + end + before do path = '/rest/builds/0.1/bulk' stub_full_request('https://gitlab-test.atlassian.net' + path, method: :post) .with(body: body, headers: expected_headers(path)) + .to_return(body: response_body, headers: { 'Content-Type': 'application/json' }) end it "calls the API with auth headers" do @@ -245,7 +371,9 @@ RSpec.describe Atlassian::JiraConnect::Client do end it 'only sends information about relevant MRs' do - expect(subject).to receive(:post).with('/rest/builds/0.1/bulk', { builds: have_attributes(size: 6) }) + expect(subject).to receive(:post) + .with('/rest/builds/0.1/bulk', { builds: have_attributes(size: 6) }) + .and_call_original subject.send(:store_build_info, project: project, pipelines: pipelines) end @@ -267,11 +395,25 @@ RSpec.describe Atlassian::JiraConnect::Client do it 'does call the API if the feature flag enabled for the project' do stub_feature_flags(jira_sync_builds: project) - expect(subject).to receive(:post).with('/rest/builds/0.1/bulk', { builds: Array }) + expect(subject).to receive(:post) + .with('/rest/builds/0.1/bulk', { builds: Array }) + .and_call_original subject.send(:store_build_info, project: project, pipelines: pipelines) end + context 'there are errors' do + let(:failures) do + [{ errors: [{ message: 'X' }, { message: 'Y' }] }, { errors: [{ message: 'Z' }] }] + end + + it 'reports the errors' do + response = subject.send(:store_build_info, project: project, pipelines: pipelines) + + expect(response['errorMessages']).to eq(%w(X Y Z)) + end + end + it 'avoids N+1 database queries' do pending 'https://gitlab.com/gitlab-org/gitlab/-/issues/292818' diff --git a/spec/lib/gitlab/experimentation/controller_concern_spec.rb b/spec/lib/gitlab/experimentation/controller_concern_spec.rb index c47f71c207d..1cebe37bea5 100644 --- a/spec/lib/gitlab/experimentation/controller_concern_spec.rb +++ b/spec/lib/gitlab/experimentation/controller_concern_spec.rb @@ -10,6 +10,10 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do use_backwards_compatible_subject_index: true }, test_experiment: { + tracking_category: 'Team', + rollout_strategy: rollout_strategy + }, + my_experiment: { tracking_category: 'Team' } } @@ -20,6 +24,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do end let(:enabled_percentage) { 10 } + let(:rollout_strategy) { nil } controller(ApplicationController) do include Gitlab::Experimentation::ControllerConcern @@ -117,6 +122,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do end context 'when subject is given' do + let(:rollout_strategy) { :user } let(:user) { build(:user) } it 'uses the subject' do @@ -244,6 +250,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do it "provides the subject's hashed global_id as label" do experiment_subject = double(:subject, to_global_id: 'abc') + allow(Gitlab::Experimentation).to receive(:valid_subject_for_rollout_strategy?).and_return(true) controller.track_experiment_event(:test_experiment, 'start', 1, subject: experiment_subject) @@ -420,6 +427,26 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do controller.record_experiment_user(:test_experiment, context) end + + context 'with a cookie based rollout strategy' do + it 'calls tracking_group with a nil subject' do + expect(controller).to receive(:tracking_group).with(:test_experiment, nil, subject: nil).and_return(:experimental) + allow(::Experiment).to receive(:add_user).with(:test_experiment, :experimental, user, context) + + controller.record_experiment_user(:test_experiment, context) + end + end + + context 'with a user based rollout strategy' do + let(:rollout_strategy) { :user } + + it 'calls tracking_group with a user subject' do + expect(controller).to receive(:tracking_group).with(:test_experiment, nil, subject: user).and_return(:experimental) + allow(::Experiment).to receive(:add_user).with(:test_experiment, :experimental, user, context) + + controller.record_experiment_user(:test_experiment, context) + end + end end context 'the user is part of the control group' do diff --git a/spec/lib/gitlab/experimentation/experiment_spec.rb b/spec/lib/gitlab/experimentation/experiment_spec.rb index 008e6699597..94dbf1d7e4b 100644 --- a/spec/lib/gitlab/experimentation/experiment_spec.rb +++ b/spec/lib/gitlab/experimentation/experiment_spec.rb @@ -9,7 +9,8 @@ RSpec.describe Gitlab::Experimentation::Experiment do let(:params) do { tracking_category: 'Category1', - use_backwards_compatible_subject_index: true + use_backwards_compatible_subject_index: true, + rollout_strategy: nil } end diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb index b503960b8c7..71d08903532 100644 --- a/spec/lib/gitlab/experimentation_spec.rb +++ b/spec/lib/gitlab/experimentation_spec.rb @@ -102,7 +102,11 @@ RSpec.describe Gitlab::Experimentation do context 'when subject has a global_id' do let(:experiment_subject) { double(:subject, to_global_id: 'z') } - it { is_expected.to eq(true) } + it do + allow(Gitlab::Experimentation).to receive(:valid_subject_for_rollout_strategy?).and_return(true) + + is_expected.to eq(true) + end end context 'when subject is nil' do @@ -150,7 +154,11 @@ RSpec.describe Gitlab::Experimentation do context 'when subject has a global_id' do let(:experiment_subject) { double(:subject, to_global_id: 'abcd') } - it { is_expected.to eq(true) } + it do + allow(Gitlab::Experimentation).to receive(:valid_subject_for_rollout_strategy?).and_return(true) + + is_expected.to eq(true) + end end context 'when subject is nil' do diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb index 8e9f7e372c5..cd89674af0f 100644 --- a/spec/lib/gitlab/path_regex_spec.rb +++ b/spec/lib/gitlab/path_regex_spec.rb @@ -102,6 +102,7 @@ RSpec.describe Gitlab::PathRegex do .concat(files_in_public) .concat(Array(API::API.prefix.to_s)) .concat(sitemap_words) + .concat(deprecated_routes) .compact .uniq end @@ -110,6 +111,11 @@ RSpec.describe Gitlab::PathRegex do %w(sitemap sitemap.xml sitemap.xml.gz) end + let(:deprecated_routes) do + # profile was deprecated in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51646 + %w(profile) + end + let(:ee_top_level_words) do %w(unsubscribes v2) end diff --git a/spec/requests/profiles/notifications_controller_spec.rb b/spec/requests/profiles/notifications_controller_spec.rb index 87669b3594c..d7dfb1c675d 100644 --- a/spec/requests/profiles/notifications_controller_spec.rb +++ b/spec/requests/profiles/notifications_controller_spec.rb @@ -24,7 +24,7 @@ RSpec.describe 'view user notifications' do get profile_notifications_path end - describe 'GET /profile/notifications' do + describe 'GET /-/profile/notifications' do it 'does not have an N+1 due to an additional groups (with no parent group)' do get_profile_notifications diff --git a/spec/routing/notifications_routing_spec.rb b/spec/routing/notifications_routing_spec.rb index 007e8ff4816..d66aa7f219f 100644 --- a/spec/routing/notifications_routing_spec.rb +++ b/spec/routing/notifications_routing_spec.rb @@ -4,15 +4,15 @@ require "spec_helper" RSpec.describe "notifications routing" do it "routes to #show" do - expect(get("/profile/notifications")).to route_to("profiles/notifications#show") + expect(get("/-/profile/notifications")).to route_to("profiles/notifications#show") end it "routes to #update" do - expect(put("/profile/notifications")).to route_to("profiles/notifications#update") + expect(put("/-/profile/notifications")).to route_to("profiles/notifications#update") end it 'routes to group #update' do - expect(put("/profile/notifications/groups/gitlab-org")).to route_to("profiles/groups#update", id: 'gitlab-org') - expect(put("/profile/notifications/groups/gitlab.org")).to route_to("profiles/groups#update", id: 'gitlab.org') + expect(put("/-/profile/notifications/groups/gitlab-org")).to route_to("profiles/groups#update", id: 'gitlab-org') + expect(put("/-/profile/notifications/groups/gitlab.org")).to route_to("profiles/groups#update", id: 'gitlab.org') end end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index a683dc28f4f..29e5c1b4bae 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -106,8 +106,8 @@ RSpec.describe 'project routing' do let(:base_path) { '/gitlab/gitlabhq/-/wikis' } end - it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/wikis", "/gitlab/gitlabhq/-/wikis" - it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/wikis/home/edit", "/gitlab/gitlabhq/-/wikis/home/edit" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/wikis", "/gitlab/gitlabhq/-/wikis" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/wikis/home/edit", "/gitlab/gitlabhq/-/wikis/home/edit" end # branches_project_repository GET /:project_id/repository/branches(.:format) projects/repositories#branches @@ -171,7 +171,7 @@ RSpec.describe 'project routing' do expect(delete('/gitlab/gitlabhq/-/tags/feature@45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') end - it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/tags", "/gitlab/gitlabhq/-/tags" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/tags", "/gitlab/gitlabhq/-/tags" end # project_deploy_keys GET /:project_id/deploy_keys(.:format) deploy_keys#index @@ -259,8 +259,8 @@ RSpec.describe 'project routing' do let(:base_path) { '/gitlab/gitlabhq/-/merge_requests' } end - it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/merge_requests", "/gitlab/gitlabhq/-/merge_requests" - it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/merge_requests/1/diffs", "/gitlab/gitlabhq/-/merge_requests/1/diffs" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/merge_requests", "/gitlab/gitlabhq/-/merge_requests" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/merge_requests/1/diffs", "/gitlab/gitlabhq/-/merge_requests/1/diffs" end describe Projects::MergeRequests::CreationsController, 'routing' do @@ -290,7 +290,7 @@ RSpec.describe 'project routing' do expect(get('/gitlab/gitlabhq/-/merge_requests/new/diffs.json')).to route_to('projects/merge_requests/creations#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'json') end - it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/merge_requests/new", "/gitlab/gitlabhq/-/merge_requests/new" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/merge_requests/new", "/gitlab/gitlabhq/-/merge_requests/new" end describe Projects::MergeRequests::DiffsController, 'routing' do @@ -454,8 +454,8 @@ RSpec.describe 'project routing' do let(:base_path) { '/gitlab/gitlabhq/-/issues' } end - it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/issues", "/gitlab/gitlabhq/-/issues" - it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/issues/1/edit", "/gitlab/gitlabhq/-/issues/1/edit" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/issues", "/gitlab/gitlabhq/-/issues" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/issues/1/edit", "/gitlab/gitlabhq/-/issues/1/edit" end # project_noteable_notes GET /:project_id/noteable/:target_type/:target_id/notes notes#index @@ -769,25 +769,25 @@ RSpec.describe 'project routing' do describe Projects::EnvironmentsController, 'routing' do describe 'legacy routing' do - it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/environments", "/gitlab/gitlabhq/-/environments" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/environments", "/gitlab/gitlabhq/-/environments" end end describe Projects::ClustersController, 'routing' do describe 'legacy routing' do - it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/clusters", "/gitlab/gitlabhq/-/clusters" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/clusters", "/gitlab/gitlabhq/-/clusters" end end describe Projects::ErrorTrackingController, 'routing' do describe 'legacy routing' do - it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/error_tracking", "/gitlab/gitlabhq/-/error_tracking" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/error_tracking", "/gitlab/gitlabhq/-/error_tracking" end end describe Projects::Serverless, 'routing' do describe 'legacy routing' do - it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/serverless", "/gitlab/gitlabhq/-/serverless" + it_behaves_like 'redirecting a legacy path', "/gitlab/gitlabhq/serverless", "/gitlab/gitlabhq/-/serverless" end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 26ad1f14786..7b9ba783885 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -122,111 +122,115 @@ RSpec.describe HelpController, "routing" do end end -# profile_account GET /profile/account(.:format) profile#account -# profile_history GET /profile/history(.:format) profile#history -# profile_password PUT /profile/password(.:format) profile#password_update -# profile_token GET /profile/token(.:format) profile#token -# profile GET /profile(.:format) profile#show -# profile_update PUT /profile/update(.:format) profile#update +# profile_account GET /-/profile/account(.:format) profile#account +# profile_history GET /-/profile/history(.:format) profile#history +# profile_password PUT /-/profile/password(.:format) profile#password_update +# profile_token GET /-/profile/token(.:format) profile#token +# profile GET /-/profile(.:format) profile#show +# profile_update PUT /-/profile/update(.:format) profile#update RSpec.describe ProfilesController, "routing" do it "to #account" do - expect(get("/profile/account")).to route_to('profiles/accounts#show') + expect(get("/-/profile/account")).to route_to('profiles/accounts#show') end + it_behaves_like 'redirecting a legacy path', '/profile/account', '/-/profile/account' it "to #audit_log" do - expect(get("/profile/audit_log")).to route_to('profiles#audit_log') + expect(get("/-/profile/audit_log")).to route_to('profiles#audit_log') end + it_behaves_like 'redirecting a legacy path', '/profile/audit_log', '/-/profile/audit_log' it "to #reset_feed_token" do - expect(put("/profile/reset_feed_token")).to route_to('profiles#reset_feed_token') + expect(put("/-/profile/reset_feed_token")).to route_to('profiles#reset_feed_token') end it "to #show" do - expect(get("/profile")).to route_to('profiles#show') - end - - it 'to #show from scope routing' do expect(get("/-/profile")).to route_to('profiles#show') end + it_behaves_like 'redirecting a legacy path', '/profile', '/-/profile' end -# profile_preferences GET /profile/preferences(.:format) profiles/preferences#show -# PATCH /profile/preferences(.:format) profiles/preferences#update -# PUT /profile/preferences(.:format) profiles/preferences#update +# profile_preferences GET /-/profile/preferences(.:format) profiles/preferences#show +# PATCH /-/profile/preferences(.:format) profiles/preferences#update +# PUT /-/profile/preferences(.:format) profiles/preferences#update RSpec.describe Profiles::PreferencesController, 'routing' do it 'to #show' do - expect(get('/profile/preferences')).to route_to('profiles/preferences#show') + expect(get('/-/profile/preferences')).to route_to('profiles/preferences#show') end + it_behaves_like 'redirecting a legacy path', '/profile/preferences', '/-/profile/preferences' it 'to #update' do - expect(put('/profile/preferences')).to route_to('profiles/preferences#update') - expect(patch('/profile/preferences')).to route_to('profiles/preferences#update') + expect(put('/-/profile/preferences')).to route_to('profiles/preferences#update') + expect(patch('/-/profile/preferences')).to route_to('profiles/preferences#update') end end -# keys GET /keys(.:format) keys#index -# POST /keys(.:format) keys#create -# edit_key GET /keys/:id/edit(.:format) keys#edit -# key GET /keys/:id(.:format) keys#show -# PUT /keys/:id(.:format) keys#update -# DELETE /keys/:id(.:format) keys#destroy +# keys GET /-/profile/keys(.:format) keys#index +# POST /-/profile/keys(.:format) keys#create +# edit_key GET /-/profile/keys/:id/edit(.:format) keys#edit +# key GET /-/profile/keys/:id(.:format) keys#show +# PUT /-/profile/keys/:id(.:format) keys#update +# DELETE /-/profile/keys/:id(.:format) keys#destroy RSpec.describe Profiles::KeysController, "routing" do it "to #index" do - expect(get("/profile/keys")).to route_to('profiles/keys#index') + expect(get("/-/profile/keys")).to route_to('profiles/keys#index') end + it_behaves_like 'redirecting a legacy path', '/profile/keys', '/-/profile/keys' it "to #create" do - expect(post("/profile/keys")).to route_to('profiles/keys#create') + expect(post("/-/profile/keys")).to route_to('profiles/keys#create') end it "to #show" do - expect(get("/profile/keys/1")).to route_to('profiles/keys#show', id: '1') + expect(get("/-/profile/keys/1")).to route_to('profiles/keys#show', id: '1') end + it_behaves_like 'redirecting a legacy path', '/profile/keys/1', '/-/profile/keys/1' it "to #destroy" do - expect(delete("/profile/keys/1")).to route_to('profiles/keys#destroy', id: '1') + expect(delete("/-/profile/keys/1")).to route_to('profiles/keys#destroy', id: '1') end end -# keys GET /gpg_keys gpg_keys#index -# key POST /gpg_keys gpg_keys#create -# PUT /gpg_keys/:id gpg_keys#revoke -# DELETE /gpg_keys/:id gpg_keys#desroy +# keys GET /-/profile/gpg_keys gpg_keys#index +# key POST /-/profile/gpg_keys gpg_keys#create +# PUT /-/profile/gpg_keys/:id gpg_keys#revoke +# DELETE /-/profile/gpg_keys/:id gpg_keys#desroy RSpec.describe Profiles::GpgKeysController, "routing" do it "to #index" do - expect(get("/profile/gpg_keys")).to route_to('profiles/gpg_keys#index') + expect(get("/-/profile/gpg_keys")).to route_to('profiles/gpg_keys#index') end + it_behaves_like 'redirecting a legacy path', '/profile/gpg_keys', '/-/profile/gpg_keys' it "to #create" do - expect(post("/profile/gpg_keys")).to route_to('profiles/gpg_keys#create') + expect(post("/-/profile/gpg_keys")).to route_to('profiles/gpg_keys#create') end it "to #destroy" do - expect(delete("/profile/gpg_keys/1")).to route_to('profiles/gpg_keys#destroy', id: '1') + expect(delete("/-/profile/gpg_keys/1")).to route_to('profiles/gpg_keys#destroy', id: '1') end end -# emails GET /emails(.:format) emails#index -# POST /keys(.:format) emails#create -# DELETE /keys/:id(.:format) keys#destroy +# emails GET /-/profile/emails(.:format) emails#index +# POST /-/profile/emails(.:format) emails#create +# DELETE /-/profile/emails/:id(.:format) keys#destroy RSpec.describe Profiles::EmailsController, "routing" do it "to #index" do - expect(get("/profile/emails")).to route_to('profiles/emails#index') + expect(get("/-/profile/emails")).to route_to('profiles/emails#index') end + it_behaves_like 'redirecting a legacy path', '/profile/emails', '/-/profile/emails' it "to #create" do - expect(post("/profile/emails")).to route_to('profiles/emails#create') + expect(post("/-/profile/emails")).to route_to('profiles/emails#create') end it "to #destroy" do - expect(delete("/profile/emails/1")).to route_to('profiles/emails#destroy', id: '1') + expect(delete("/-/profile/emails/1")).to route_to('profiles/emails#destroy', id: '1') end end -# profile_avatar DELETE /profile/avatar(.:format) profiles/avatars#destroy +# profile_avatar DELETE /-/profile/avatar(.:format) profiles/avatars#destroy RSpec.describe Profiles::AvatarsController, "routing" do it "to #destroy" do - expect(delete("/profile/avatar")).to route_to('profiles/avatars#destroy') + expect(delete("/-/profile/avatar")).to route_to('profiles/avatars#destroy') end end diff --git a/spec/services/jira_connect/sync_service_spec.rb b/spec/services/jira_connect/sync_service_spec.rb index 4b434348146..edd0bad70f5 100644 --- a/spec/services/jira_connect/sync_service_spec.rb +++ b/spec/services/jira_connect/sync_service_spec.rb @@ -45,11 +45,11 @@ RSpec.describe JiraConnect::SyncService do it 'logs the response as an error' do expect_next(client).to store_info([ { 'errorMessages' => ['some error message'] }, - { 'rejectedBuilds' => ['x'] } + { 'errorMessages' => ['x'] } ]) expect_log(:error, { 'errorMessages' => ['some error message'] }) - expect_log(:error, { 'rejectedBuilds' => ['x'] }) + expect_log(:error, { 'errorMessages' => ['x'] }) subject end diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index a15f6bdbe2c..a6730c5de52 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -79,6 +79,19 @@ RSpec.describe Projects::UpdatePagesService do end end + it 'fails if sha on branch was updated before deployment was uploaded' do + expect(subject).to receive(:create_pages_deployment).and_wrap_original do |m, *args| + build.update!(ref: 'feature') + m.call(*args) + end + + expect(execute).not_to eq(:success) + expect(project.pages_metadatum).not_to be_deployed + + expect(deploy_status).to be_failed + expect(deploy_status.description).to eq('build SHA is outdated for this ref') + end + it 'does not fail if pages_metadata is absent' do project.pages_metadatum.destroy! project.reload diff --git a/spec/support/shared_examples/routing/legacy_path_redirect_shared_examples.rb b/spec/support/shared_examples/routing/legacy_path_redirect_shared_examples.rb index 808336db7b1..ae3f6425b5e 100644 --- a/spec/support/shared_examples/routing/legacy_path_redirect_shared_examples.rb +++ b/spec/support/shared_examples/routing/legacy_path_redirect_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples 'redirecting a legacy project path' do |source, target| +RSpec.shared_examples 'redirecting a legacy path' do |source, target| include RSpec::Rails::RequestExampleGroup it "redirects #{source} to #{target}" do diff --git a/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb b/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb index 01bd856e2a8..e6592f7f204 100644 --- a/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb +++ b/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do + using RSpec::Parameterized::TableSyntax + let_it_be(:repository, reload: true) { create(:container_repository, :cleanup_scheduled) } let_it_be(:project) { repository.project } let_it_be(:policy) { project.container_expiration_policy } @@ -42,11 +44,32 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do service_response = cleanup_service_response(status: :unfinished, repository: repository, cleanup_tags_service_after_truncate_size: 10, cleanup_tags_service_before_delete_size: 5) expect(ContainerExpirationPolicies::CleanupService) .to receive(:new).with(repository).and_return(double(execute: service_response)) - expect_log_extra_metadata(service_response: service_response, cleanup_status: :unfinished) + expect_log_extra_metadata(service_response: service_response, cleanup_status: :unfinished, truncated: true) subject end end + + context 'the truncated log field' do + where(:before_truncate_size, :after_truncate_size, :truncated) do + 100 | 100 | false + 100 | 80 | true + nil | 100 | false + 100 | nil | false + nil | nil | false + end + + with_them do + it 'is logged properly' do + service_response = cleanup_service_response(status: :unfinished, repository: repository, cleanup_tags_service_after_truncate_size: after_truncate_size, cleanup_tags_service_before_truncate_size: before_truncate_size) + expect(ContainerExpirationPolicies::CleanupService) + .to receive(:new).with(repository).and_return(double(execute: service_response)) + expect_log_extra_metadata(service_response: service_response, cleanup_status: :unfinished, truncated: truncated) + + subject + end + end + end end context 'with policy running shortly' do @@ -189,13 +212,14 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do ) end - def expect_log_extra_metadata(service_response:, cleanup_status: :finished) + def expect_log_extra_metadata(service_response:, cleanup_status: :finished, truncated: false) expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_status, cleanup_status) expect(worker).to receive(:log_extra_metadata_on_done).with(:container_repository_id, repository.id) - expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_tags_service_original_size, service_response.payload[:cleanup_tags_service_original_size]) - expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_tags_service_before_truncate_size, service_response.payload[:cleanup_tags_service_before_truncate_size]) - expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_tags_service_after_truncate_size, service_response.payload[:cleanup_tags_service_after_truncate_size]) - expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_tags_service_before_delete_size, service_response.payload[:cleanup_tags_service_before_delete_size]) + %i[cleanup_tags_service_original_size cleanup_tags_service_before_truncate_size cleanup_tags_service_after_truncate_size cleanup_tags_service_before_delete_size].each do |field| + value = service_response.payload[field] + expect(worker).to receive(:log_extra_metadata_on_done).with(field, value) unless value.nil? + end + expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_tags_service_truncated, truncated) end end |