diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-20 21:09:05 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-20 21:09:05 +0300 |
commit | 883d5720994852248f18cb3053dc9f053f28d6f9 (patch) | |
tree | 409c976ddc659f34afaae3b2e97f1d0325f6455c /spec | |
parent | 5e97da08cba997aefba6f6d13850f95536a80477 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
33 files changed, 638 insertions, 240 deletions
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb index fdd11b59938..87236530046 100644 --- a/spec/features/abuse_report_spec.rb +++ b/spec/features/abuse_report_spec.rb @@ -2,17 +2,43 @@ require 'spec_helper' -RSpec.describe 'Abuse reports', feature_category: :not_owned do - let(:another_user) { create(:user) } +RSpec.describe 'Abuse reports', feature_category: :insider_threat do + let_it_be(:another_user) { create(:user) } + + let_it_be(:project) { create(:project, :public) } + let_it_be(:issue) { create(:issue, project: project, author: another_user) } before do sign_in(create(:user)) end - it 'report abuse' do + it 'report abuse from an issue', :js do + visit project_issue_path(project, issue) + + click_button 'Issue actions' + click_link 'Report abuse to administrator' + + wait_for_requests + + fill_in 'abuse_report_message', with: 'This user sends spam' + click_button 'Send report' + + expect(page).to have_content 'Thank you for your report' + visit user_path(another_user) - click_link 'Report abuse' + expect(page).to have_button('Already reported for abuse') + end + + it 'report abuse from profile', :js do + visit user_path(another_user) + + click_button 'Report abuse to administrator' + + choose "They're posting spam." + click_button 'Next' + + wait_for_requests fill_in 'abuse_report_message', with: 'This user sends spam' click_button 'Send report' @@ -21,6 +47,6 @@ RSpec.describe 'Abuse reports', feature_category: :not_owned do visit user_path(another_user) - expect(page).to have_button("Already reported for abuse") + expect(page).to have_button('Already reported for abuse') end end diff --git a/spec/features/nav/new_nav_toggle_spec.rb b/spec/features/nav/new_nav_toggle_spec.rb index f040d801cfb..8e5cc7df053 100644 --- a/spec/features/nav/new_nav_toggle_spec.rb +++ b/spec/features/nav/new_nav_toggle_spec.rb @@ -48,14 +48,19 @@ RSpec.describe 'new navigation toggle', :js, feature_category: :navigation do expect(user.reload.use_new_navigation).to eq true end + + it 'shows the old navigation' do + expect(page).to have_selector('.js-navbar') + expect(page).not_to have_selector('[data-testid="super-sidebar"]') + end end context 'when user has new nav enabled' do let(:user_preference) { true } it 'allows to disable new nav', :aggregate_failures do - within '.js-nav-user-dropdown' do - find('a[data-toggle="dropdown"]').click + within '[data-testid="super-sidebar"] [data-testid="user-dropdown"]' do + find('button').click expect(page).to have_content('Navigation redesign') toggle = page.find('.gl-toggle.is-checked') @@ -66,6 +71,11 @@ RSpec.describe 'new navigation toggle', :js, feature_category: :navigation do expect(user.reload.use_new_navigation).to eq false end + + it 'shows the new navigation' do + expect(page).not_to have_selector('.js-navbar') + expect(page).to have_selector('[data-testid="super-sidebar"]') + end end end end diff --git a/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js b/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js new file mode 100644 index 00000000000..4f66348f9cd --- /dev/null +++ b/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js @@ -0,0 +1,125 @@ +import { GlDrawer, GlForm, GlFormGroup, GlFormRadioGroup } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; + +import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; + +jest.mock('~/lib/utils/common_utils', () => ({ + contentTop: jest.fn(), +})); + +jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); + +describe('AbuseCategorySelector', () => { + let wrapper; + + const ACTION_PATH = '/abuse_reports/add_category'; + const USER_ID = '1'; + const REPORTED_FROM_URL = 'http://example.com'; + + const createComponent = (props) => { + wrapper = shallowMountExtended(AbuseCategorySelector, { + propsData: { + ...props, + }, + provide: { + formSubmitPath: ACTION_PATH, + userId: USER_ID, + reportedFromUrl: REPORTED_FROM_URL, + }, + }); + }; + + beforeEach(() => { + createComponent({ showDrawer: true }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const findDrawer = () => wrapper.findComponent(GlDrawer); + const findTitle = () => wrapper.findByTestId('category-drawer-title'); + + const findForm = () => wrapper.findComponent(GlForm); + const findFormGroup = () => wrapper.findComponent(GlFormGroup); + const findRadioGroup = () => wrapper.findComponent(GlFormRadioGroup); + + const findCSRFToken = () => findForm().find('input[name="authenticity_token"]'); + const findUserId = () => wrapper.findByTestId('input-user-id'); + const findReferer = () => wrapper.findByTestId('input-referer'); + + const findSubmitFormButton = () => wrapper.findByTestId('submit-form-button'); + + describe('Drawer', () => { + it('is open when prop showDrawer = true', () => { + expect(findDrawer().exists()).toBe(true); + expect(findDrawer().props('open')).toBe(true); + }); + + it('renders title', () => { + expect(findTitle().text()).toBe(wrapper.vm.$options.i18n.title); + }); + + it('emits close-drawer event', async () => { + await findDrawer().vm.$emit('close'); + + expect(wrapper.emitted('close-drawer')).toHaveLength(1); + }); + + describe('when props showDrawer = false', () => { + beforeEach(() => { + createComponent({ showDrawer: false }); + }); + + it('hides the drawer', () => { + expect(findDrawer().props('open')).toBe(false); + }); + }); + }); + + describe('Select category form', () => { + it('renders POST form with path', () => { + expect(findForm().attributes()).toMatchObject({ + method: 'post', + action: ACTION_PATH, + }); + }); + + it('renders csrf token', () => { + expect(findCSRFToken().attributes('value')).toBe('mock-csrf-token'); + }); + + it('renders label', () => { + expect(findFormGroup().exists()).toBe(true); + expect(findFormGroup().attributes('label')).toBe(wrapper.vm.$options.i18n.label); + }); + + it('renders radio group', () => { + expect(findRadioGroup().exists()).toBe(true); + expect(findRadioGroup().props('options')).toEqual(wrapper.vm.$options.categoryOptions); + expect(findRadioGroup().attributes('name')).toBe('abuse_report[category]'); + expect(findRadioGroup().attributes('required')).not.toBeUndefined(); + }); + + it('renders userId as a hidden fields', () => { + expect(findUserId().attributes()).toMatchObject({ + type: 'hidden', + name: 'user_id', + value: USER_ID, + }); + }); + + it('renders referer as a hidden fields', () => { + expect(findReferer().attributes()).toMatchObject({ + type: 'hidden', + name: 'ref_url', + value: REPORTED_FROM_URL, + }); + }); + + it('renders submit button', () => { + expect(findSubmitFormButton().exists()).toBe(true); + expect(findSubmitFormButton().text()).toBe(wrapper.vm.$options.i18n.next); + }); + }); +}); diff --git a/spec/frontend/behaviors/markdown/render_gfm_spec.js b/spec/frontend/behaviors/markdown/render_gfm_spec.js new file mode 100644 index 00000000000..0bbb92282e5 --- /dev/null +++ b/spec/frontend/behaviors/markdown/render_gfm_spec.js @@ -0,0 +1,9 @@ +import { renderGFM } from '~/behaviors/markdown/render_gfm'; + +describe('renderGFM', () => { + it('handles a missing element', () => { + expect(() => { + renderGFM(); + }).not.toThrow(); + }); +}); diff --git a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js index 17bf465baf3..0821c59c8a0 100644 --- a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js +++ b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js @@ -1,5 +1,5 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import CiBadge from '~/vue_shared/components/ci_badge_link.vue'; +import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; import PipelineScheduleLastPipeline from '~/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline.vue'; import { mockPipelineScheduleNodes } from '../../../mock_data'; @@ -18,7 +18,7 @@ describe('Pipeline schedule last pipeline', () => { }); }; - const findCIBadge = () => wrapper.findComponent(CiBadge); + const findCIBadgeLink = () => wrapper.findComponent(CiBadgeLink); const findStatusText = () => wrapper.findByTestId('pipeline-schedule-status-text'); afterEach(() => { @@ -28,8 +28,10 @@ describe('Pipeline schedule last pipeline', () => { it('displays pipeline status', () => { createComponent(); - expect(findCIBadge().exists()).toBe(true); - expect(findCIBadge().props('status')).toBe(defaultProps.schedule.lastPipeline.detailedStatus); + expect(findCIBadgeLink().exists()).toBe(true); + expect(findCIBadgeLink().props('status')).toBe( + defaultProps.schedule.lastPipeline.detailedStatus, + ); expect(findStatusText().exists()).toBe(false); }); @@ -37,6 +39,6 @@ describe('Pipeline schedule last pipeline', () => { createComponent({ schedule: mockPipelineScheduleNodes[0] }); expect(findStatusText().text()).toBe('None'); - expect(findCIBadge().exists()).toBe(false); + expect(findCIBadgeLink().exists()).toBe(false); }); }); diff --git a/spec/frontend/gfm_auto_complete/mock_data.js b/spec/frontend/gfm_auto_complete/mock_data.js index 9c5a9d7ef3d..d58ccaf0f39 100644 --- a/spec/frontend/gfm_auto_complete/mock_data.js +++ b/spec/frontend/gfm_auto_complete/mock_data.js @@ -37,8 +37,8 @@ export const crmContactsMock = [ { id: 1, email: 'contact.1@email.com', - firstName: 'Contact', - lastName: 'One', + first_name: 'Contact', + last_name: 'One', search: 'contact.1@email.com', state: 'active', set: false, @@ -46,8 +46,8 @@ export const crmContactsMock = [ { id: 2, email: 'contact.2@email.com', - firstName: 'Contact', - lastName: 'Two', + first_name: 'Contact', + last_name: 'Two', search: 'contact.2@email.com', state: 'active', set: false, @@ -55,8 +55,8 @@ export const crmContactsMock = [ { id: 3, email: 'contact.3@email.com', - firstName: 'Contact', - lastName: 'Three', + first_name: 'Contact', + last_name: 'Three', search: 'contact.3@email.com', state: 'inactive', set: false, @@ -64,8 +64,8 @@ export const crmContactsMock = [ { id: 4, email: 'contact.4@email.com', - firstName: 'Contact', - lastName: 'Four', + first_name: 'Contact', + last_name: 'Four', search: 'contact.4@email.com', state: 'inactive', set: true, @@ -73,8 +73,8 @@ export const crmContactsMock = [ { id: 5, email: 'contact.5@email.com', - firstName: 'Contact', - lastName: 'Five', + first_name: 'Contact', + last_name: 'Five', search: 'contact.5@email.com', state: 'active', set: true, @@ -82,8 +82,8 @@ export const crmContactsMock = [ { id: 5, email: 'contact.6@email.com', - firstName: 'Contact', - lastName: 'Six', + first_name: 'Contact', + last_name: 'Six', search: 'contact.6@email.com', state: 'active', set: undefined, // On purpose diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js index eeef92d4183..cc2dc084e47 100644 --- a/spec/frontend/gfm_auto_complete_spec.js +++ b/spec/frontend/gfm_auto_complete_spec.js @@ -4,6 +4,7 @@ import $ from 'jquery'; import labelsFixture from 'test_fixtures/autocomplete_sources/labels.json'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import GfmAutoComplete, { + escape, membersBeforeSave, highlighter, CONTACT_STATE_ACTIVE, @@ -21,6 +22,20 @@ import { crmContactsMock, } from 'ee_else_ce_jest/gfm_auto_complete/mock_data'; +describe('escape', () => { + it.each` + xssPayload | escapedPayload + ${'<script>alert(1)</script>'} | ${'<script>alert(1)</script>'} + ${'%3Cscript%3E alert(1) %3C%2Fscript%3E'} | ${'<script> alert(1) </script>'} + ${'%253Cscript%253E alert(1) %253C%252Fscript%253E'} | ${'<script> alert(1) </script>'} + `( + 'escapes the input string correctly accounting for multiple encoding', + ({ xssPayload, escapedPayload }) => { + expect(escape(xssPayload)).toBe(escapedPayload); + }, + ); +}); + describe('GfmAutoComplete', () => { const fetchDataMock = { fetchData: jest.fn() }; let gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call(fetchDataMock); @@ -590,7 +605,7 @@ describe('GfmAutoComplete', () => { id: 5, title: '${search}<script>oh no $', // eslint-disable-line no-template-curly-in-string }), - ).toBe('<li><small>5</small> ${search}<script>oh no $</li>'); + ).toBe('<li><small>5</small> &dollar;{search}<script>oh no &dollar;</li>'); }); }); @@ -636,7 +651,7 @@ describe('GfmAutoComplete', () => { availabilityStatus: '', }), ).toBe( - '<li>IMG my-group <small>${search}<script>oh no $</small> <i class="icon"/></li>', + '<li>IMG my-group <small>&dollar;{search}<script>oh no &dollar;</small> <i class="icon"/></li>', ); }); @@ -813,7 +828,7 @@ describe('GfmAutoComplete', () => { const title = '${search}<script>oh no $'; // eslint-disable-line no-template-curly-in-string expect(GfmAutoComplete.Labels.templateFunction(color, title)).toBe( - '<li><span class="dropdown-label-box" style="background: #123456"></span> ${search}<script>oh no $</li>', + '<li><span class="dropdown-label-box" style="background: #123456"></span> &dollar;{search}<script>oh no &dollar;</li>', ); }); }); @@ -868,7 +883,7 @@ describe('GfmAutoComplete', () => { const title = '${search}<script>oh no $'; // eslint-disable-line no-template-curly-in-string expect(GfmAutoComplete.Milestones.templateFunction(title, expired)).toBe( - '<li>${search}<script>oh no $</li>', + '<li>&dollar;{search}<script>oh no &dollar;</li>', ); }); }); @@ -925,7 +940,9 @@ describe('GfmAutoComplete', () => { const expectContacts = ({ input, output }) => { triggerDropdown(input); - expect(getDropdownItems()).toEqual(output.map((contact) => contact.email)); + expect(getDropdownItems()).toEqual( + output.map((contact) => `${contact.first_name} ${contact.last_name} ${contact.email}`), + ); }; describe('with no contacts assigned', () => { diff --git a/spec/frontend/jobs/components/table/jobs_table_spec.js b/spec/frontend/jobs/components/table/jobs_table_spec.js index 803df3df37f..3c4f2d624fe 100644 --- a/spec/frontend/jobs/components/table/jobs_table_spec.js +++ b/spec/frontend/jobs/components/table/jobs_table_spec.js @@ -2,14 +2,14 @@ import { GlTable } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import JobsTable from '~/jobs/components/table/jobs_table.vue'; -import CiBadge from '~/vue_shared/components/ci_badge_link.vue'; +import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; import { mockJobsNodes } from '../../mock_data'; describe('Jobs Table', () => { let wrapper; const findTable = () => wrapper.findComponent(GlTable); - const findStatusBadge = () => wrapper.findComponent(CiBadge); + const findCiBadgeLink = () => wrapper.findComponent(CiBadgeLink); const findTableRows = () => wrapper.findAllByTestId('jobs-table-row'); const findJobStage = () => wrapper.findByTestId('job-stage-name'); const findJobName = () => wrapper.findByTestId('job-name'); @@ -43,7 +43,7 @@ describe('Jobs Table', () => { }); it('displays job status', () => { - expect(findStatusBadge().exists()).toBe(true); + expect(findCiBadgeLink().exists()).toBe(true); }); it('displays the job stage and name', () => { diff --git a/spec/frontend/members/components/table/role_dropdown_spec.js b/spec/frontend/members/components/table/role_dropdown_spec.js index b254cce4d72..3815064b3f6 100644 --- a/spec/frontend/members/components/table/role_dropdown_spec.js +++ b/spec/frontend/members/components/table/role_dropdown_spec.js @@ -4,11 +4,14 @@ import { within } from '@testing-library/dom'; import { mount, createWrapper } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; +import waitForPromises from 'helpers/wait_for_promises'; import RoleDropdown from '~/members/components/table/role_dropdown.vue'; import { MEMBER_TYPES } from '~/members/constants'; +import { guestOverageConfirmAction } from 'ee_else_ce/members/guest_overage_confirm_action'; import { member } from '../../mock_data'; Vue.use(Vuex); +jest.mock('ee_else_ce/members/guest_overage_confirm_action'); describe('RoleDropdown', () => { let wrapper; @@ -63,12 +66,21 @@ describe('RoleDropdown', () => { const findDropdownToggle = () => wrapper.find('button[aria-haspopup="true"]'); const findDropdown = () => wrapper.findComponent(GlDropdown); + let originalGon; + + beforeEach(() => { + originalGon = window.gon; + gon.features = { showOverageOnRolePromotion: true }; + }); + afterEach(() => { + window.gon = originalGon; wrapper.destroy(); }); describe('when dropdown is open', () => { beforeEach(() => { + guestOverageConfirmAction.mockReturnValue(true); createComponent(); return findDropdownToggle().trigger('click'); @@ -117,8 +129,12 @@ describe('RoleDropdown', () => { await getDropdownItemByText('Developer').trigger('click'); expect(findDropdown().props('disabled')).toBe(true); + }); - await nextTick(); + it('enables dropdown after `updateMemberRole` resolves', async () => { + await getDropdownItemByText('Developer').trigger('click'); + + await waitForPromises(); expect(findDropdown().props('disabled')).toBe(false); }); @@ -148,4 +164,44 @@ describe('RoleDropdown', () => { expect(findDropdown().props('right')).toBe(false); }); + + describe('guestOverageConfirmAction', () => { + const mockConfirmAction = ({ confirmed }) => { + guestOverageConfirmAction.mockResolvedValueOnce(confirmed); + }; + + beforeEach(() => { + createComponent(); + + findDropdownToggle().trigger('click'); + }); + + afterEach(() => { + guestOverageConfirmAction.mockReset(); + }); + + describe('when guestOverageConfirmAction returns true', () => { + beforeEach(() => { + mockConfirmAction({ confirmed: true }); + + getDropdownItemByText('Reporter').trigger('click'); + }); + + it('calls updateMemberRole', () => { + expect(actions.updateMemberRole).toHaveBeenCalled(); + }); + }); + + describe('when guestOverageConfirmAction returns false', () => { + beforeEach(() => { + mockConfirmAction({ confirmed: false }); + + getDropdownItemByText('Reporter').trigger('click'); + }); + + it('does not call updateMemberRole', () => { + expect(actions.updateMemberRole).not.toHaveBeenCalled(); + }); + }); + }); }); diff --git a/spec/frontend/members/guest_overage_confirm_action_spec.js b/spec/frontend/members/guest_overage_confirm_action_spec.js new file mode 100644 index 00000000000..d7ab54fa13b --- /dev/null +++ b/spec/frontend/members/guest_overage_confirm_action_spec.js @@ -0,0 +1,7 @@ +import { guestOverageConfirmAction } from '~/members/guest_overage_confirm_action'; + +describe('guestOverageConfirmAction', () => { + it('returns true', () => { + expect(guestOverageConfirmAction()).toBe(true); + }); +}); diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js index 740037a5ac8..9359bd9b95f 100644 --- a/spec/frontend/pipelines/pipelines_table_spec.js +++ b/spec/frontend/pipelines/pipelines_table_spec.js @@ -17,7 +17,7 @@ import { TRACKING_CATEGORIES, } from '~/pipelines/constants'; -import CiBadge from '~/vue_shared/components/ci_badge_link.vue'; +import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; jest.mock('~/pipelines/event_hub'); @@ -50,7 +50,7 @@ describe('Pipelines Table', () => { }; const findGlTableLite = () => wrapper.findComponent(GlTableLite); - const findStatusBadge = () => wrapper.findComponent(CiBadge); + const findCiBadgeLink = () => wrapper.findComponent(CiBadgeLink); const findPipelineInfo = () => wrapper.findComponent(PipelineUrl); const findTriggerer = () => wrapper.findComponent(PipelineTriggerer); const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph); @@ -97,7 +97,7 @@ describe('Pipelines Table', () => { describe('status cell', () => { it('should render a status badge', () => { - expect(findStatusBadge().exists()).toBe(true); + expect(findCiBadgeLink().exists()).toBe(true); }); }); @@ -171,7 +171,7 @@ describe('Pipelines Table', () => { }); it('tracks status badge click', () => { - findStatusBadge().vm.$emit('ciStatusBadgeClick'); + findCiBadgeLink().vm.$emit('ciStatusBadgeClick'); expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_ci_status_badge', { label: TRACKING_CATEGORIES.table, diff --git a/spec/frontend/repository/commits_service_spec.js b/spec/frontend/repository/commits_service_spec.js index de7c56f239a..b7343bf3a7e 100644 --- a/spec/frontend/repository/commits_service_spec.js +++ b/spec/frontend/repository/commits_service_spec.js @@ -4,6 +4,7 @@ import { loadCommits, isRequested, resetRequestedCommits } from '~/repository/co import httpStatus from '~/lib/utils/http_status'; import { createAlert } from '~/flash'; import { I18N_COMMIT_DATA_FETCH_ERROR } from '~/repository/constants'; +import { refWithSpecialCharMock, encodedRefWithSpecialCharMock } from './mock_data'; jest.mock('~/flash'); @@ -39,10 +40,11 @@ describe('commits service', () => { expect(axios.get).toHaveBeenCalledWith(testUrl, { params: { format: 'json', offset } }); }); - it('encodes the path correctly', async () => { - await requestCommits(1, 'some-project', 'with $peci@l ch@rs/'); + it('encodes the path and ref', async () => { + const encodedUrl = `/some-project/-/refs/${encodedRefWithSpecialCharMock}/logs_tree/with%20%24peci%40l%20ch%40rs%2F`; + + await requestCommits(1, 'some-project', 'with $peci@l ch@rs/', refWithSpecialCharMock); - const encodedUrl = '/some-project/-/refs/main/logs_tree/with%20%24peci%40l%20ch%40rs%2F'; expect(axios.get).toHaveBeenCalledWith(encodedUrl, expect.anything()); }); diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js index cda47a5b0a5..c1b5f89c37f 100644 --- a/spec/frontend/repository/mock_data.js +++ b/spec/frontend/repository/mock_data.js @@ -87,6 +87,8 @@ export const applicationInfoMock = { gitpodEnabled: true }; export const propsMock = { path: 'some_file.js', projectPath: 'some/path' }; export const refMock = 'default-ref'; +export const refWithSpecialCharMock = 'selected-#-ref'; +export const encodedRefWithSpecialCharMock = encodeURIComponent(refWithSpecialCharMock); export const blobControlsDataMock = { id: '1234', diff --git a/spec/frontend/repository/utils/ref_switcher_utils_spec.js b/spec/frontend/repository/utils/ref_switcher_utils_spec.js index 3335059554f..4d0250fffbf 100644 --- a/spec/frontend/repository/utils/ref_switcher_utils_spec.js +++ b/spec/frontend/repository/utils/ref_switcher_utils_spec.js @@ -1,5 +1,6 @@ import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils'; import setWindowLocation from 'helpers/set_window_location_helper'; +import { refWithSpecialCharMock, encodedRefWithSpecialCharMock } from '../mock_data'; const projectRootPath = 'root/Project1'; const currentRef = 'main'; @@ -19,4 +20,10 @@ describe('generateRefDestinationPath', () => { setWindowLocation(currentPath); expect(generateRefDestinationPath(projectRootPath, selectedRef)).toBe(result); }); + + it('encodes the selected ref', () => { + const result = `${projectRootPath}/-/tree/${encodedRefWithSpecialCharMock}`; + + expect(generateRefDestinationPath(projectRootPath, refWithSpecialCharMock)).toBe(result); + }); }); diff --git a/spec/frontend/users/profile/components/report_abuse_button_spec.js b/spec/frontend/users/profile/components/report_abuse_button_spec.js new file mode 100644 index 00000000000..bd39a089473 --- /dev/null +++ b/spec/frontend/users/profile/components/report_abuse_button_spec.js @@ -0,0 +1,72 @@ +import { GlButton } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; + +import ReportAbuseButton from '~/users/profile/components/report_abuse_button.vue'; +import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; + +describe('ReportAbuseButton', () => { + let wrapper; + + const ACTION_PATH = '/abuse_reports/add_category'; + const USER_ID = '1'; + const REPORTED_FROM_URL = 'http://example.com'; + + const createComponent = (props) => { + wrapper = shallowMountExtended(ReportAbuseButton, { + propsData: { + ...props, + }, + provide: { + formSubmitPath: ACTION_PATH, + userId: USER_ID, + reportedFromUrl: REPORTED_FROM_URL, + }, + }); + }; + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const findReportAbuseButton = () => wrapper.findComponent(GlButton); + const findAbuseCategorySelector = () => wrapper.findComponent(AbuseCategorySelector); + + it('renders report abuse button', () => { + expect(findReportAbuseButton().exists()).toBe(true); + + expect(findReportAbuseButton().props()).toMatchObject({ + category: 'primary', + icon: 'error', + }); + + expect(findReportAbuseButton().attributes('aria-label')).toBe( + wrapper.vm.$options.i18n.reportAbuse, + ); + }); + + it('renders abuse category selector with the drawer initially closed', () => { + expect(findAbuseCategorySelector().exists()).toBe(true); + + expect(findAbuseCategorySelector().props('showDrawer')).toBe(false); + }); + + describe('when button is clicked', () => { + beforeEach(async () => { + await findReportAbuseButton().vm.$emit('click'); + }); + + it('opens the abuse category selector', () => { + expect(findAbuseCategorySelector().props('showDrawer')).toBe(true); + }); + + it('closes the abuse category selector', async () => { + await findAbuseCategorySelector().vm.$emit('close-drawer'); + + expect(findAbuseCategorySelector().props('showDrawer')).toBe(false); + }); + }); +}); diff --git a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap deleted file mode 100644 index 4077564486c..00000000000 --- a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap +++ /dev/null @@ -1,163 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MRWidgetAutoMergeEnabled template should have correct elements 1`] = ` -<div - class="mr-widget-body media gl-display-flex gl-align-items-center" -> - <div - class="gl-w-6 gl-h-6 gl-display-flex gl-align-self-start gl-mr-3" - > - <div - class="gl-display-flex gl-m-auto" - > - <div - class="gl-mr-3 gl-p-2 gl-m-0! gl-text-blue-500 gl-w-6 gl-p-2" - > - <div - class="gl-rounded-full gl-relative gl-display-flex mr-widget-extension-icon" - > - <div - class="gl-absolute gl-top-half gl-left-50p gl-translate-x-n50 gl-display-flex gl-m-auto" - > - <div - class="gl-display-flex gl-m-auto gl-translate-y-n50" - > - <svg - aria-label="Scheduled " - class="gl-display-block gl-icon s12" - data-qa-selector="status_scheduled_icon" - data-testid="status-scheduled-icon" - role="img" - > - <use - href="#status-scheduled" - /> - </svg> - </div> - </div> - </div> - </div> - </div> - </div> - - <div - class="gl-display-flex gl-w-full" - > - <div - class="media-body gl-display-flex gl-align-items-center" - > - - <h4 - class="gl-mr-3" - data-testid="statusText" - > - Set by to be merged automatically when the pipeline succeeds - </h4> - - <div - class="gl-display-flex gl-font-size-0 gl-ml-auto gl-gap-3" - > - <div - class="gl-display-flex gl-align-items-flex-start" - > - <div - class="dropdown b-dropdown gl-dropdown gl-display-block gl-md-display-none! btn-group" - lazy="" - no-caret="" - title="Options" - > - <!----> - <button - aria-expanded="false" - aria-haspopup="true" - class="btn dropdown-toggle btn-default btn-sm gl-p-2! gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret" - type="button" - > - <!----> - - <svg - aria-hidden="true" - class="dropdown-icon gl-icon s16" - data-testid="ellipsis_v-icon" - role="img" - > - <use - href="#ellipsis_v" - /> - </svg> - - <span - class="gl-dropdown-button-text gl-sr-only" - > - - </span> - - <svg - aria-hidden="true" - class="gl-button-icon dropdown-chevron gl-icon s16" - data-testid="chevron-down-icon" - role="img" - > - <use - href="#chevron-down" - /> - </svg> - </button> - <ul - class="dropdown-menu dropdown-menu-right" - role="menu" - tabindex="-1" - > - <!----> - </ul> - </div> - - <button - class="btn gl-display-none gl-md-display-block gl-float-left btn-confirm btn-sm gl-button btn-confirm-tertiary js-cancel-auto-merge" - data-qa-selector="cancel_auto_merge_button" - data-testid="cancelAutomaticMergeButton" - type="button" - > - <!----> - - <!----> - - <span - class="gl-button-text" - > - - Cancel auto-merge - - </span> - </button> - </div> - </div> - </div> - - <div - class="gl-md-display-none gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6 gl-mt-1" - > - <button - class="btn gl-vertical-align-top btn-default btn-sm gl-button btn-default-tertiary btn-icon" - title="Collapse merge details" - type="button" - > - <!----> - - <svg - aria-hidden="true" - class="gl-button-icon gl-icon s16" - data-testid="chevron-lg-up-icon" - role="img" - > - <use - href="#chevron-lg-up" - /> - </svg> - - <!----> - </button> - </div> - </div> -</div> -`; diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js index 5b9f30dfb86..fef5fee5f19 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js @@ -128,14 +128,6 @@ describe('MRWidgetAutoMergeEnabled', () => { }); describe('template', () => { - it('should have correct elements', () => { - factory({ - ...defaultMrProps(), - }); - - expect(wrapper.element).toMatchSnapshot(); - }); - it('should disable cancel auto merge button when the action is in progress', async () => { factory({ ...defaultMrProps(), diff --git a/spec/frontend/vue_shared/components/ci_badge_link_spec.js b/spec/frontend/vue_shared/components/ci_badge_link_spec.js index 07cbfe1e79b..4f24ec2d015 100644 --- a/spec/frontend/vue_shared/components/ci_badge_link_spec.js +++ b/spec/frontend/vue_shared/components/ci_badge_link_spec.js @@ -1,6 +1,6 @@ import { GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import CiBadge from '~/vue_shared/components/ci_badge_link.vue'; +import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; jest.mock('~/lib/utils/url_utility', () => ({ @@ -79,7 +79,7 @@ describe('CI Badge Link Component', () => { const findIcon = () => wrapper.findComponent(CiIcon); const createComponent = (propsData) => { - wrapper = shallowMount(CiBadge, { propsData }); + wrapper = shallowMount(CiBadgeLink, { propsData }); }; afterEach(() => { diff --git a/spec/graphql/types/description_version_type_spec.rb b/spec/graphql/types/description_version_type_spec.rb new file mode 100644 index 00000000000..36bb1af7f7b --- /dev/null +++ b/spec/graphql/types/description_version_type_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['DescriptionVersion'], feature_category: :team_planning do + it { expect(described_class).to have_graphql_field(:id) } + it { expect(described_class).to have_graphql_field(:description) } + + specify { expect(described_class).to require_graphql_authorizations(:read_issuable) } +end diff --git a/spec/graphql/types/notes/note_type_spec.rb b/spec/graphql/types/notes/note_type_spec.rb index cbf7f086dbe..dd364be5ae2 100644 --- a/spec/graphql/types/notes/note_type_spec.rb +++ b/spec/graphql/types/notes/note_type_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GitlabSchema.types['Note'] do +RSpec.describe GitlabSchema.types['Note'], feature_category: :team_planning do it 'exposes the expected fields' do expected_fields = %i[ author @@ -24,6 +24,7 @@ RSpec.describe GitlabSchema.types['Note'] do updated_at user_permissions url + system_note_metadata ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/notes/system_note_metadata_type_spec.rb b/spec/graphql/types/notes/system_note_metadata_type_spec.rb new file mode 100644 index 00000000000..d243e926ff5 --- /dev/null +++ b/spec/graphql/types/notes/system_note_metadata_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['SystemNoteMetadata'], feature_category: :team_planning do + it { expect(described_class).to have_graphql_field(:id) } + it { expect(described_class).to have_graphql_field(:action) } + it { expect(described_class).to have_graphql_field(:description_version) } + + specify { expect(described_class).to require_graphql_authorizations(:read_note) } +end diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb index 4a37e17fb08..adf784360c2 100644 --- a/spec/helpers/nav_helper_spec.rb +++ b/spec/helpers/nav_helper_spec.rb @@ -134,4 +134,62 @@ RSpec.describe NavHelper do it { is_expected.to eq(true) } end end + + describe '#show_super_sidebar?' do + shared_examples '#show_super_sidebar returns false' do + it 'returns false' do + expect(helper.show_super_sidebar?).to eq(false) + end + end + + it 'returns false by default' do + allow(helper).to receive(:current_user).and_return(nil) + + expect(helper.show_super_sidebar?).to be_falsy + end + + context 'when used is signed-in' do + let_it_be(:user) { create(:user) } + + before do + allow(helper).to receive(:current_user).and_return(user) + stub_feature_flags(super_sidebar_nav: new_nav_ff) + user.update!(use_new_navigation: user_preference) + end + + context 'with feature flag off' do + let(:new_nav_ff) { false } + + context 'when user has new nav disabled' do + let(:user_preference) { false } + + it_behaves_like '#show_super_sidebar returns false' + end + + context 'when user has new nav enabled' do + let(:user_preference) { true } + + it_behaves_like '#show_super_sidebar returns false' + end + end + + context 'with feature flag on' do + let(:new_nav_ff) { true } + + context 'when user has new nav disabled' do + let(:user_preference) { false } + + it_behaves_like '#show_super_sidebar returns false' + end + + context 'when user has new nav enabled' do + let(:user_preference) { true } + + it 'returns true' do + expect(helper.show_super_sidebar?).to eq(true) + end + end + end + end + end end diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb index 414cbb169b9..67252eed938 100644 --- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb @@ -16,12 +16,14 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do let(:policy) { nil } let(:key) { 'some key' } let(:when_config) { nil } + let(:unprotect) { false } let(:config) do { key: key, untracked: true, - paths: ['some/path/'] + paths: ['some/path/'], + unprotect: unprotect }.tap do |config| config[:policy] = policy if policy config[:when] = when_config if when_config @@ -31,7 +33,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do describe '#value' do shared_examples 'hash key value' do it 'returns hash value' do - expect(entry.value).to eq(key: key, untracked: true, paths: ['some/path/'], policy: 'pull-push', when: 'on_success') + expect(entry.value).to eq(key: key, untracked: true, paths: ['some/path/'], policy: 'pull-push', when: 'on_success', unprotect: false) end end @@ -57,6 +59,14 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do end end + context 'with option `unprotect` specified' do + let(:unprotect) { true } + + it 'returns true' do + expect(entry.value).to match(a_hash_including(unprotect: true)) + end + end + context 'with `policy`' do where(:policy, :result) do 'pull-push' | 'pull-push' diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 69c0d05dcdd..c1b9bd58d98 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -631,7 +631,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job, feature_category: :pipeline_autho it 'overrides default config' do expect(entry[:image].value).to eq(name: 'some_image') - expect(entry[:cache].value).to eq([key: 'test', policy: 'pull-push', when: 'on_success']) + expect(entry[:cache].value).to eq([key: 'test', policy: 'pull-push', when: 'on_success', unprotect: false]) end end @@ -646,7 +646,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job, feature_category: :pipeline_autho it 'uses config from default entry' do expect(entry[:image].value).to eq 'specified' - expect(entry[:cache].value).to eq([key: 'test', policy: 'pull-push', when: 'on_success']) + expect(entry[:cache].value).to eq([key: 'test', policy: 'pull-push', when: 'on_success', unprotect: false]) end end diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb index c40589104cd..9722609aef6 100644 --- a/spec/lib/gitlab/ci/config/entry/root_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb @@ -127,7 +127,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: 'image:1.0' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }], + cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success', + unprotect: false }], job_variables: {}, root_variables_inheritance: true, ignore: false, @@ -142,7 +143,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: 'image:1.0' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }], + cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success', + unprotect: false }], job_variables: {}, root_variables_inheritance: true, ignore: false, @@ -158,7 +160,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do release: { name: "Release $CI_TAG_NAME", tag_name: 'v0.06', description: "./release_changelog.txt" }, image: { name: "image:1.0" }, services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }], - cache: [{ key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' }], + cache: [{ key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success', + unprotect: false }], only: { refs: %w(branches tags) }, job_variables: { 'VAR' => { value: 'job' } }, root_variables_inheritance: true, @@ -206,7 +209,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: 'image:1.0' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }], + cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success', unprotect: false }], job_variables: {}, root_variables_inheritance: true, ignore: false, @@ -219,7 +222,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: 'image:1.0' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }], + cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success', unprotect: false }], job_variables: { 'VAR' => { value: 'job' } }, root_variables_inheritance: true, ignore: false, @@ -274,7 +277,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do describe '#cache_value' do it 'returns correct cache definition' do - expect(root.cache_value).to eq([key: 'a', policy: 'pull-push', when: 'on_success']) + expect(root.cache_value).to eq([key: 'a', policy: 'pull-push', when: 'on_success', unprotect: false]) end end end diff --git a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb index fb8020bf43e..c264ea3bece 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb @@ -212,6 +212,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do paths: ['vendor/ruby'], untracked: true, policy: 'push', + unprotect: true, when: 'on_success' } end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index ae98d2e0cad..41c51340eb6 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1699,7 +1699,8 @@ module Gitlab untracked: true, key: 'key', policy: 'pull-push', - when: 'on_success' + when: 'on_success', + unprotect: false ]) end @@ -1723,7 +1724,8 @@ module Gitlab untracked: true, key: { files: ['file'] }, policy: 'pull-push', - when: 'on_success' + when: 'on_success', + unprotect: false ]) end @@ -1749,14 +1751,16 @@ module Gitlab untracked: true, key: 'keya', policy: 'pull-push', - when: 'on_success' + when: 'on_success', + unprotect: false }, { paths: ['logs/', 'binaries/'], untracked: true, key: 'key', policy: 'pull-push', - when: 'on_success' + when: 'on_success', + unprotect: false } ] ) @@ -1783,7 +1787,8 @@ module Gitlab untracked: true, key: { files: ['file'] }, policy: 'pull-push', - when: 'on_success' + when: 'on_success', + unprotect: false ]) end @@ -1808,7 +1813,8 @@ module Gitlab untracked: true, key: { files: ['file'], prefix: 'prefix' }, policy: 'pull-push', - when: 'on_success' + when: 'on_success', + unprotect: false ]) end @@ -1831,7 +1837,8 @@ module Gitlab untracked: false, key: 'local', policy: 'pull-push', - when: 'on_success' + when: 'on_success', + unprotect: false ]) end end diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index 3871b18fdd5..32cf3b4c505 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AbuseReport do +RSpec.describe AbuseReport, feature_category: :insider_threat do let_it_be(:report, reload: true) { create(:abuse_report) } let_it_be(:user, reload: true) { create(:admin) } @@ -24,6 +24,7 @@ RSpec.describe AbuseReport do it { is_expected.to validate_presence_of(:user) } it { is_expected.to validate_presence_of(:message) } it { is_expected.to validate_uniqueness_of(:user_id).with_message('has already been reported') } + it { is_expected.to validate_presence_of(:category) } end describe '#remove_user' do @@ -54,4 +55,21 @@ RSpec.describe AbuseReport do report.notify end end + + describe 'enums' do + let(:categories) do + { + spam: 1, + offensive: 2, + phishing: 3, + crypto: 4, + credentials: 5, + copyright: 6, + malware: 7, + other: 8 + } + end + + it { is_expected.to define_enum_for(:category).with_values(**categories) } + end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index a9b322a1a16..534875a9bba 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1136,6 +1136,19 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do it do is_expected.to all(a_hash_including(key: a_string_matching(/-protected$/))) end + + context 'and the cache has the `unprotect` option' do + let(:options) do + { cache: [ + { key: "key", paths: ["public"], policy: "pull-push", unprotect: true }, + { key: "key2", paths: ["public"], policy: "pull-push", unprotect: true } + ] } + end + + it do + is_expected.to all(a_hash_including(key: a_string_matching(/-non_protected$/))) + end + end end context 'when pipeline is not on a protected ref' do diff --git a/spec/requests/abuse_reports_controller_spec.rb b/spec/requests/abuse_reports_controller_spec.rb index 510855d95e0..71ecf8444bf 100644 --- a/spec/requests/abuse_reports_controller_spec.rb +++ b/spec/requests/abuse_reports_controller_spec.rb @@ -40,6 +40,80 @@ RSpec.describe AbuseReportsController, feature_category: :users do end end + describe 'POST add_category', :aggregate_failures do + subject(:request) { post add_category_abuse_reports_path, params: request_params } + + let(:abuse_category) { 'spam' } + + context 'when user is reported for abuse' do + let(:ref_url) { 'http://example.com' } + let(:request_params) { { user_id: user.id, abuse_report: { category: abuse_category }, ref_url: ref_url } } + + it 'renders new template' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:new) + end + + it 'sets the instance variables' do + subject + + expect(assigns(:abuse_report)).to be_kind_of(AbuseReport) + expect(assigns(:abuse_report)).to have_attributes( + user_id: user.id, + category: abuse_category + ) + expect(assigns(:ref_url)).to eq(ref_url) + end + end + + context 'when abuse_report is missing in params' do + let(:request_params) { { user_id: user.id } } + + it 'raises an error' do + expect { subject }.to raise_error(ActionController::ParameterMissing) + end + end + + context 'when user_id is missing in params' do + let(:request_params) { { abuse_report: { category: abuse_category } } } + + it 'redirects the reporter to root_path' do + subject + + expect(response).to redirect_to root_path + expect(flash[:alert]).to eq(_('Cannot create the abuse report. The user has been deleted.')) + end + end + + context 'when the user has already been deleted' do + let(:request_params) { { user_id: user.id, abuse_report: { category: abuse_category } } } + + it 'redirects the reporter to root_path' do + user.destroy! + + subject + + expect(response).to redirect_to root_path + expect(flash[:alert]).to eq(_('Cannot create the abuse report. The user has been deleted.')) + end + end + + context 'when the user has already been blocked' do + let(:request_params) { { user_id: user.id, abuse_report: { category: abuse_category } } } + + it 'redirects the reporter to the user\'s profile' do + user.block + + subject + + expect(response).to redirect_to user + expect(flash[:alert]).to eq(_('Cannot create the abuse report. This user has been blocked.')) + end + end + end + describe 'POST create' do context 'with valid attributes' do it 'saves the abuse report' do diff --git a/spec/requests/api/debian_project_packages_spec.rb b/spec/requests/api/debian_project_packages_spec.rb index c27e165b39b..5258d26be17 100644 --- a/spec/requests/api/debian_project_packages_spec.rb +++ b/spec/requests/api/debian_project_packages_spec.rb @@ -5,7 +5,17 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d include HttpBasicAuthHelpers include WorkhorseHelpers - include_context 'Debian repository shared context', :project, true do + include_context 'Debian repository shared context', :project, false do + shared_examples 'accept GET request on private project with access to package registry for everyone' do + include_context 'Debian repository access', :private, :anonymous, :basic do + before do + container.project_feature.reload.update!(package_registry_access_level: ProjectFeature::PUBLIC) + end + + it_behaves_like 'Debian packages GET request', :success + end + end + context 'with invalid parameter' do let(:url) { "/projects/1/packages/debian/dists/with+space/InRelease" } @@ -16,54 +26,63 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/Release.gpg" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^-----BEGIN PGP SIGNATURE-----/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/Release' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/Release" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Codename: fixture-distribution\n$/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/InRelease' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/InRelease" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^-----BEGIN PGP SIGNED MESSAGE-----/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/binary-:architecture/Packages' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/binary-#{architecture.name}/Packages" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /Description: This is an incomplete Packages file/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/binary-:architecture/by-hash/SHA256/:file_sha256' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/binary-#{architecture.name}/by-hash/SHA256/#{component_file_older_sha256.file_sha256}" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Other SHA256$/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end - describe 'GET projects/:id/packages/debian/dists/*distribution/source/Sources' do + describe 'GET projects/:id/packages/debian/dists/*distribution/:component/source/Sources' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/source/Sources" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /Description: This is an incomplete Sources file/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end - describe 'GET projects/:id/packages/debian/dists/*distribution/source/by-hash/SHA256/:file_sha256' do + describe 'GET projects/:id/packages/debian/dists/*distribution/:component/source/by-hash/SHA256/:file_sha256' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/source/by-hash/SHA256/#{component_file_sources_older_sha256.file_sha256}" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Other SHA256$/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/debian-installer/binary-:architecture/Packages' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/debian-installer/binary-#{architecture.name}/Packages" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /Description: This is an incomplete D-I Packages file/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/debian-installer/binary-:architecture/by-hash/SHA256/:file_sha256' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/debian-installer/binary-#{architecture.name}/by-hash/SHA256/#{component_file_di_older_sha256.file_sha256}" } it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Other SHA256$/ + it_behaves_like 'accept GET request on private project with access to package registry for everyone' end describe 'GET projects/:id/packages/debian/pool/:codename/:letter/:package_name/:package_version/:file_name' do @@ -90,6 +109,10 @@ RSpec.describe API::DebianProjectPackages, feature_category: :package_registry d end end end + + it_behaves_like 'accept GET request on private project with access to package registry for everyone' do + let(:file_name) { 'sample_1.2.3~alpha2.dsc' } + end end describe 'PUT projects/:id/packages/debian/:file_name' do diff --git a/spec/requests/api/graphql/project/work_items_spec.rb b/spec/requests/api/graphql/project/work_items_spec.rb index a59da706a8a..de35c943749 100644 --- a/spec/requests/api/graphql/project/work_items_spec.rb +++ b/spec/requests/api/graphql/project/work_items_spec.rb @@ -263,7 +263,7 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team GRAPHQL end - before do + before_all do create_notes(item1, "some note1") create_notes(item2, "some note2") end diff --git a/spec/services/ci/create_pipeline_service/cache_spec.rb b/spec/services/ci/create_pipeline_service/cache_spec.rb index 82c3d374636..f9640f99031 100644 --- a/spec/services/ci/create_pipeline_service/cache_spec.rb +++ b/spec/services/ci/create_pipeline_service/cache_spec.rb @@ -37,6 +37,7 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes paths: ['logs/', 'binaries/'], policy: 'pull-push', untracked: true, + unprotect: false, when: 'on_success' } @@ -69,7 +70,8 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes key: /[a-f0-9]{40}/, paths: ['logs/'], policy: 'pull-push', - when: 'on_success' + when: 'on_success', + unprotect: false } expect(pipeline).to be_persisted @@ -85,7 +87,8 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes key: /default/, paths: ['logs/'], policy: 'pull-push', - when: 'on_success' + when: 'on_success', + unprotect: false } expect(pipeline).to be_persisted @@ -118,7 +121,8 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes key: /\$ENV_VAR-[a-f0-9]{40}/, paths: ['logs/'], policy: 'pull-push', - when: 'on_success' + when: 'on_success', + unprotect: false } expect(pipeline).to be_persisted @@ -134,7 +138,8 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes key: /\$ENV_VAR-default/, paths: ['logs/'], policy: 'pull-push', - when: 'on_success' + when: 'on_success', + unprotect: false } expect(pipeline).to be_persisted |