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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-04 18:17:13 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-04 18:17:13 +0300
commit4bdfcf93f224edb9c4daff90d95b0c6c92766ea3 (patch)
treecedf1f94561571d00033c48846ad3959af64449b /spec
parentfb5d3cceb8d43f8c2dc22a5d8c74327e9397f2e8 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/factories/service_desk/custom_email_verification.rb5
-rw-r--r--spec/features/issues/user_bulk_edits_issues_labels_spec.rb4
-rw-r--r--spec/features/issues/user_bulk_edits_issues_spec.rb14
-rw-r--r--spec/features/merge_requests/user_mass_updates_spec.rb8
-rw-r--r--spec/fixtures/packages/npm/payload_with_empty_attachment.json29
-rw-r--r--spec/frontend/import_entities/import_projects/components/bitbucket_status_table_spec.js17
-rw-r--r--spec/frontend/import_entities/import_projects/components/github_organizations_box_spec.js97
-rw-r--r--spec/frontend/import_entities/import_projects/components/github_status_table_spec.js125
-rw-r--r--spec/frontend/import_entities/import_projects/store/actions_spec.js14
-rw-r--r--spec/frontend/import_entities/import_projects/store/mutations_spec.js13
-rw-r--r--spec/frontend/issues/list/components/issues_list_app_spec.js4
-rw-r--r--spec/frontend/vue_compat_test_setup.js9
-rw-r--r--spec/models/service_desk/custom_email_verification_spec.rb157
-rw-r--r--spec/requests/api/npm_project_packages_spec.rb8
-rw-r--r--spec/services/packages/npm/create_package_service_spec.rb7
15 files changed, 428 insertions, 83 deletions
diff --git a/spec/factories/service_desk/custom_email_verification.rb b/spec/factories/service_desk/custom_email_verification.rb
index 614be5da71e..3f3a2ea570d 100644
--- a/spec/factories/service_desk/custom_email_verification.rb
+++ b/spec/factories/service_desk/custom_email_verification.rb
@@ -2,7 +2,10 @@
FactoryBot.define do
factory :service_desk_custom_email_verification, class: '::ServiceDesk::CustomEmailVerification' do
+ state { 'started' }
+ token { 'XXXXXXXXXXXX' }
project
- state { "running" }
+ triggerer factory: :user
+ triggered_at { Time.current }
end
end
diff --git a/spec/features/issues/user_bulk_edits_issues_labels_spec.rb b/spec/features/issues/user_bulk_edits_issues_labels_spec.rb
index 1fc6609d1f5..a01ae9ae0c2 100644
--- a/spec/features/issues/user_bulk_edits_issues_labels_spec.rb
+++ b/spec/features/issues/user_bulk_edits_issues_labels_spec.rb
@@ -406,7 +406,7 @@ RSpec.describe 'Issues > Labels bulk assignment', feature_category: :team_planni
context 'cannot bulk assign labels' do
it do
- expect(page).not_to have_button 'Edit issues'
+ expect(page).not_to have_button 'Bulk edit'
expect(page).not_to have_unchecked_field 'Select all'
expect(page).not_to have_unchecked_field issue1.title
end
@@ -462,7 +462,7 @@ RSpec.describe 'Issues > Labels bulk assignment', feature_category: :team_planni
def enable_bulk_update
visit project_issues_path(project)
wait_for_requests
- click_button 'Edit issues'
+ click_button 'Bulk edit'
end
def disable_bulk_update
diff --git a/spec/features/issues/user_bulk_edits_issues_spec.rb b/spec/features/issues/user_bulk_edits_issues_spec.rb
index 5696bde4069..3e119d86c05 100644
--- a/spec/features/issues/user_bulk_edits_issues_spec.rb
+++ b/spec/features/issues/user_bulk_edits_issues_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor
it 'sets to closed', :js do
visit project_issues_path(project)
- click_button 'Edit issues'
+ click_button 'Bulk edit'
check 'Select all'
click_button 'Select status'
click_button 'Closed'
@@ -29,7 +29,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor
create_closed
visit project_issues_path(project, state: 'closed')
- click_button 'Edit issues'
+ click_button 'Bulk edit'
check 'Select all'
click_button 'Select status'
click_button 'Open'
@@ -43,7 +43,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor
it 'updates to current user' do
visit project_issues_path(project)
- click_button 'Edit issues'
+ click_button 'Bulk edit'
check 'Select all'
click_update_assignee_button
click_button user.username
@@ -61,7 +61,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor
expect(find('.issue:first-of-type')).to have_link "Assigned to #{user.name}"
- click_button 'Edit issues'
+ click_button 'Bulk edit'
check 'Select all'
click_update_assignee_button
click_button 'Unassigned'
@@ -77,7 +77,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor
it 'updates milestone' do
visit project_issues_path(project)
- click_button 'Edit issues'
+ click_button 'Bulk edit'
check 'Select all'
click_button 'Select milestone'
click_button milestone.title
@@ -94,7 +94,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor
expect(find('.issue:first-of-type')).to have_text milestone.title
- click_button 'Edit issues'
+ click_button 'Bulk edit'
check 'Select all'
click_button 'Select milestone'
click_button 'No milestone'
@@ -110,7 +110,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js, feature_categor
it 'after selecting all issues, unchecking one issue only unselects that one issue' do
visit project_issues_path(project)
- click_button 'Edit issues'
+ click_button 'Bulk edit'
check 'Select all'
uncheck issue.title
diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb
index b0be76d386a..45d57cf8374 100644
--- a/spec/features/merge_requests/user_mass_updates_spec.rb
+++ b/spec/features/merge_requests/user_mass_updates_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe 'Merge requests > User mass updates', :js, feature_category: :cod
merge_request.close
visit project_merge_requests_path(project, state: 'merged')
- click_button 'Edit merge requests'
+ click_button 'Bulk edit'
expect(page).not_to have_button 'Select status'
end
@@ -108,7 +108,7 @@ RSpec.describe 'Merge requests > User mass updates', :js, feature_category: :cod
end
def change_status(text)
- click_button 'Edit merge requests'
+ click_button 'Bulk edit'
check 'Select all'
click_button 'Select status'
click_button text
@@ -116,7 +116,7 @@ RSpec.describe 'Merge requests > User mass updates', :js, feature_category: :cod
end
def change_assignee(text)
- click_button 'Edit merge requests'
+ click_button 'Bulk edit'
check 'Select all'
within 'aside[aria-label="Bulk update"]' do
click_button 'Select assignee'
@@ -127,7 +127,7 @@ RSpec.describe 'Merge requests > User mass updates', :js, feature_category: :cod
end
def change_milestone(text)
- click_button 'Edit merge requests'
+ click_button 'Bulk edit'
check 'Select all'
click_button 'Select milestone'
click_button text
diff --git a/spec/fixtures/packages/npm/payload_with_empty_attachment.json b/spec/fixtures/packages/npm/payload_with_empty_attachment.json
new file mode 100644
index 00000000000..299ff32baf3
--- /dev/null
+++ b/spec/fixtures/packages/npm/payload_with_empty_attachment.json
@@ -0,0 +1,29 @@
+{
+ "_id": "@root/npm-test",
+ "name": "@root/npm-test",
+ "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
+ "dist-tags": {
+ "latest": "1.0.1"
+ },
+ "versions": {
+ "1.0.1": {
+ "name": "@root/npm-test",
+ "version": "1.0.1",
+ "main": "app.js",
+ "dependencies": {
+ "express": "^4.16.4"
+ },
+ "dist": {
+ "shasum": "f572d396fae9206628714fb2ce00f72e94f2258f",
+ "tarball": "http://localhost/npm/package.tgz"
+ }
+ }
+ },
+ "_attachments": {
+ "@root/npm-test-1.0.1.tgz": {
+ "data": ""
+ }
+ },
+ "id": "10",
+ "package_name": "@root/npm-test"
+}
diff --git a/spec/frontend/import_entities/import_projects/components/bitbucket_status_table_spec.js b/spec/frontend/import_entities/import_projects/components/bitbucket_status_table_spec.js
index 5773ab33e05..246c6499a97 100644
--- a/spec/frontend/import_entities/import_projects/components/bitbucket_status_table_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/bitbucket_status_table_spec.js
@@ -14,11 +14,11 @@ const ImportProjectsTableStub = {
describe('BitbucketStatusTable', () => {
let wrapper;
- function createComponent(propsData, importProjectsTableStub = true, slots) {
+ function createComponent(propsData, slots) {
wrapper = shallowMount(BitbucketStatusTable, {
propsData,
stubs: {
- ImportProjectsTable: importProjectsTableStub,
+ ImportProjectsTable: ImportProjectsTableStub,
},
slots,
});
@@ -30,20 +30,23 @@ describe('BitbucketStatusTable', () => {
});
it('passes alert in incompatible-repos-warning slot', () => {
- createComponent({ providerTitle: 'Test' }, ImportProjectsTableStub);
+ createComponent({ providerTitle: 'Test' });
expect(wrapper.findComponent(GlAlert).exists()).toBe(true);
});
it('passes actions slot to import project table component', () => {
const actionsSlotContent = 'DEMO';
- createComponent({ providerTitle: 'Test' }, ImportProjectsTableStub, {
- actions: actionsSlotContent,
- });
+ createComponent(
+ { providerTitle: 'Test' },
+ {
+ actions: actionsSlotContent,
+ },
+ );
expect(wrapper.findComponent(ImportProjectsTable).text()).toBe(actionsSlotContent);
});
it('dismisses alert when requested', async () => {
- createComponent({ providerTitle: 'Test' }, ImportProjectsTableStub);
+ createComponent({ providerTitle: 'Test' });
wrapper.findComponent(GlAlert).vm.$emit('dismiss');
await nextTick();
diff --git a/spec/frontend/import_entities/import_projects/components/github_organizations_box_spec.js b/spec/frontend/import_entities/import_projects/components/github_organizations_box_spec.js
new file mode 100644
index 00000000000..b6f96cd6a23
--- /dev/null
+++ b/spec/frontend/import_entities/import_projects/components/github_organizations_box_spec.js
@@ -0,0 +1,97 @@
+import { GlCollapsibleListbox } from '@gitlab/ui';
+import MockAdapter from 'axios-mock-adapter';
+import { mount } from '@vue/test-utils';
+import { captureException } from '@sentry/browser';
+import { nextTick } from 'vue';
+
+import axios from '~/lib/utils/axios_utils';
+import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
+import { createAlert } from '~/alert';
+
+import GithubOrganizationsBox from '~/import_entities/import_projects/components/github_organizations_box.vue';
+
+jest.mock('@sentry/browser');
+jest.mock('~/alert');
+
+const MOCK_RESPONSE = {
+ provider_groups: [{ name: 'alpha-1' }, { name: 'alpha-2' }, { name: 'beta-1' }],
+};
+
+describe('GithubOrganizationsBox component', () => {
+ let wrapper;
+ let mockAxios;
+
+ const findListbox = () => wrapper.findComponent(GlCollapsibleListbox);
+ const mockGithubGroupPath = '/mock/groups.json';
+
+ const createComponent = (props) => {
+ wrapper = mount(GithubOrganizationsBox, {
+ propsData: {
+ value: 'some-org',
+ ...props,
+ },
+ provide: () => ({
+ statusImportGithubGroupPath: mockGithubGroupPath,
+ }),
+ });
+ };
+
+ beforeEach(() => {
+ mockAxios = new MockAdapter(axios);
+ mockAxios.onGet(mockGithubGroupPath).reply(HTTP_STATUS_OK, MOCK_RESPONSE);
+ });
+
+ afterEach(() => {
+ mockAxios.restore();
+ });
+
+ it('has underlying listbox as loading while loading organizations', () => {
+ createComponent();
+ expect(findListbox().props('loading')).toBe(true);
+ });
+
+ it('clears underlying listbox when loading is complete', async () => {
+ createComponent();
+ await axios.waitForAll();
+ expect(findListbox().props('loading')).toBe(false);
+ });
+
+ it('sets toggle-text to all organizations when selection is not provided', () => {
+ createComponent({ value: '' });
+ expect(findListbox().props('toggleText')).toBe(GithubOrganizationsBox.i18n.allOrganizations);
+ });
+
+ it('sets toggle-text to organization name when it is provided', () => {
+ const ORG_NAME = 'org';
+ createComponent({ value: ORG_NAME });
+
+ expect(findListbox().props('toggleText')).toBe(ORG_NAME);
+ });
+
+ it('emits selected organization from underlying listbox', () => {
+ createComponent();
+
+ findListbox().vm.$emit('select', 'org-id');
+ expect(wrapper.emitted('input').at(-1)).toStrictEqual(['org-id']);
+ });
+
+ it('filters list for underlying listbox', async () => {
+ createComponent();
+ await axios.waitForAll();
+
+ findListbox().vm.$emit('search', 'alpha');
+ await nextTick();
+
+ // 2 matches + 'All organizations'
+ expect(findListbox().props('items')).toHaveLength(3);
+ });
+
+ it('reports error to sentry on load', async () => {
+ mockAxios.onGet(mockGithubGroupPath).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+ createComponent();
+ await axios.waitForAll();
+
+ expect(captureException).toHaveBeenCalled();
+ expect(createAlert).toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/import_entities/import_projects/components/github_status_table_spec.js b/spec/frontend/import_entities/import_projects/components/github_status_table_spec.js
new file mode 100644
index 00000000000..7eebff7364c
--- /dev/null
+++ b/spec/frontend/import_entities/import_projects/components/github_status_table_spec.js
@@ -0,0 +1,125 @@
+import { GlTabs, GlSearchBoxByClick } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+import Vuex from 'vuex';
+
+import { stubComponent } from 'helpers/stub_component';
+import GithubStatusTable from '~/import_entities/import_projects/components/github_status_table.vue';
+import GithubOrganizationsBox from '~/import_entities/import_projects/components/github_organizations_box.vue';
+import ImportProjectsTable from '~/import_entities/import_projects/components/import_projects_table.vue';
+import initialState from '~/import_entities/import_projects/store/state';
+import * as getters from '~/import_entities/import_projects/store/getters';
+
+const ImportProjectsTableStub = stubComponent(ImportProjectsTable, {
+ importAllButtonText: 'IMPORT_ALL_TEXT',
+ methods: {
+ showImportAllModal: jest.fn(),
+ },
+ template:
+ '<div><slot name="filter" v-bind="{ importAllButtonText: $options.importAllButtonText, showImportAllModal }"></slot></div>',
+});
+
+Vue.use(Vuex);
+
+describe('GithubStatusTable', () => {
+ let wrapper;
+
+ const setFilterAction = jest.fn().mockImplementation(({ state }, filter) => {
+ state.filter = { ...state.filter, ...filter };
+ });
+
+ const findFilterField = () => wrapper.findComponent(GlSearchBoxByClick);
+ const selectTab = (idx) => {
+ wrapper.findComponent(GlTabs).vm.$emit('input', idx);
+ return nextTick();
+ };
+
+ function createComponent() {
+ const store = new Vuex.Store({
+ state: { ...initialState() },
+ getters,
+ actions: {
+ setFilter: setFilterAction,
+ },
+ });
+
+ wrapper = mount(GithubStatusTable, {
+ store,
+ propsData: {
+ providerTitle: 'Github',
+ },
+ stubs: {
+ ImportProjectsTable: ImportProjectsTableStub,
+ GithubOrganizationsBox: stubComponent(GithubOrganizationsBox),
+ GlTabs: false,
+ GlTab: false,
+ },
+ });
+ }
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders import table component', () => {
+ expect(wrapper.findComponent(ImportProjectsTable).exists()).toBe(true);
+ });
+
+ it('sets relation_type filter to owned repositories by default', () => {
+ expect(setFilterAction).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.objectContaining({ relation_type: 'owned' }),
+ );
+ });
+
+ it('updates relation_type and resets organization filter when tab is switched', async () => {
+ const NEW_ACTIVE_TAB_IDX = 1;
+ await selectTab(NEW_ACTIVE_TAB_IDX);
+
+ expect(setFilterAction).toHaveBeenCalledTimes(2);
+ expect(setFilterAction).toHaveBeenCalledWith(expect.anything(), {
+ ...GithubStatusTable.relationTypes[NEW_ACTIVE_TAB_IDX].backendFilter,
+ organization_login: '',
+ filter: '',
+ });
+ });
+
+ it('renders name filter disabled when tab with organization filter is selected and organization is not set', async () => {
+ const NEW_ACTIVE_TAB_IDX = GithubStatusTable.relationTypes.findIndex(
+ (entry) => entry.showOrganizationFilter,
+ );
+ await selectTab(NEW_ACTIVE_TAB_IDX);
+ expect(findFilterField().props('disabled')).toBe(true);
+ });
+
+ it('enables name filter disabled when organization is set', async () => {
+ const NEW_ACTIVE_TAB_IDX = GithubStatusTable.relationTypes.findIndex(
+ (entry) => entry.showOrganizationFilter,
+ );
+ await selectTab(NEW_ACTIVE_TAB_IDX);
+
+ wrapper.findComponent(GithubOrganizationsBox).vm.$emit('input', 'some-org');
+ await nextTick();
+
+ expect(findFilterField().props('disabled')).toBe(false);
+ });
+
+ it('updates filter when search box is changed', async () => {
+ const NEW_FILTER = 'test';
+ findFilterField().vm.$emit('submit', NEW_FILTER);
+ await nextTick();
+
+ expect(setFilterAction).toHaveBeenCalledWith(expect.anything(), {
+ filter: NEW_FILTER,
+ });
+ });
+
+ it('updates organization_login filter when GithubOrganizationsBox emits input', () => {
+ const NEW_ORG = 'some-org';
+ wrapper.findComponent(GithubOrganizationsBox).vm.$emit('input', NEW_ORG);
+
+ expect(setFilterAction).toHaveBeenCalledWith(expect.anything(), {
+ organization_login: NEW_ORG,
+ });
+ });
+});
diff --git a/spec/frontend/import_entities/import_projects/store/actions_spec.js b/spec/frontend/import_entities/import_projects/store/actions_spec.js
index f78016eefcf..3b94db37801 100644
--- a/spec/frontend/import_entities/import_projects/store/actions_spec.js
+++ b/spec/frontend/import_entities/import_projects/store/actions_spec.js
@@ -220,12 +220,14 @@ describe('import_projects store actions', () => {
describe('when rate limited', () => {
it('commits RECEIVE_REPOS_ERROR and shows rate limited error message', async () => {
- mock.onGet(`${TEST_HOST}/endpoint.json?filter=filter`).reply(HTTP_STATUS_TOO_MANY_REQUESTS);
+ mock
+ .onGet(`${TEST_HOST}/endpoint.json?filtered_field=filter`)
+ .reply(HTTP_STATUS_TOO_MANY_REQUESTS);
await testAction(
fetchRepos,
null,
- { ...localState, filter: 'filter' },
+ { ...localState, filter: { filtered_field: 'filter' } },
[{ type: REQUEST_REPOS }, { type: RECEIVE_REPOS_ERROR }],
[],
);
@@ -238,12 +240,12 @@ describe('import_projects store actions', () => {
describe('when filtered', () => {
it('fetches repos with filter applied', () => {
- mock.onGet(`${TEST_HOST}/endpoint.json?filter=filter`).reply(HTTP_STATUS_OK, payload);
+ mock.onGet(`${TEST_HOST}/endpoint.json?some_filter=filter`).reply(HTTP_STATUS_OK, payload);
return testAction(
fetchRepos,
null,
- { ...localState, filter: 'filter' },
+ { ...localState, filter: { some_filter: 'filter' } },
[
{ type: REQUEST_REPOS },
{ type: SET_PAGE, payload: 1 },
@@ -374,12 +376,12 @@ describe('import_projects store actions', () => {
describe('when filtered', () => {
beforeEach(() => {
- localState.filter = 'filter';
+ localState.filter = { some_filter: 'filter' };
});
it('fetches realtime changes with filter applied', () => {
mock
- .onGet(`${TEST_HOST}/endpoint.json?filter=filter`)
+ .onGet(`${TEST_HOST}/endpoint.json?some_filter=filter`)
.reply(HTTP_STATUS_OK, updatedProjects);
return testAction(
diff --git a/spec/frontend/import_entities/import_projects/store/mutations_spec.js b/spec/frontend/import_entities/import_projects/store/mutations_spec.js
index 514a168553a..07d247630cc 100644
--- a/spec/frontend/import_entities/import_projects/store/mutations_spec.js
+++ b/spec/frontend/import_entities/import_projects/store/mutations_spec.js
@@ -25,7 +25,7 @@ describe('import_projects store mutations', () => {
beforeEach(() => {
state = {
- filter: 'some-value',
+ filter: { someField: 'some-value' },
repositories: ['some', ' repositories'],
pageInfo: {
page: 1,
@@ -47,6 +47,17 @@ describe('import_projects store mutations', () => {
expect(state.pageInfo.endCursor).toBe(null);
expect(state.pageInfo.hasNextPage).toBe(true);
});
+
+ it('merges filter updates', () => {
+ const originalFilter = { ...state.filter };
+ const anotherFilter = { anotherField: 'another-value' };
+ mutations[types.SET_FILTER](state, anotherFilter);
+
+ expect(state.filter).toStrictEqual({
+ ...originalFilter,
+ ...anotherFilter,
+ });
+ });
});
describe(`${types.REQUEST_REPOS}`, () => {
diff --git a/spec/frontend/issues/list/components/issues_list_app_spec.js b/spec/frontend/issues/list/components/issues_list_app_spec.js
index 076fdc4a991..af24b547545 100644
--- a/spec/frontend/issues/list/components/issues_list_app_spec.js
+++ b/spec/frontend/issues/list/components/issues_list_app_spec.js
@@ -304,13 +304,13 @@ describe('CE IssuesListApp component', () => {
it('renders when user has permissions', () => {
wrapper = mountComponent({ provide: { canBulkUpdate: true }, mountFn: mount });
- expect(findGlButton().text()).toBe('Edit issues');
+ expect(findGlButton().text()).toBe('Bulk edit');
});
it('does not render when user does not have permissions', () => {
wrapper = mountComponent({ provide: { canBulkUpdate: false }, mountFn: mount });
- expect(findGlButtons().filter((button) => button.text() === 'Edit issues')).toHaveLength(0);
+ expect(findGlButtons().filter((button) => button.text() === 'Bulk edit')).toHaveLength(0);
});
it('emits "issuables:enableBulkEdit" event to legacy bulk edit class', async () => {
diff --git a/spec/frontend/vue_compat_test_setup.js b/spec/frontend/vue_compat_test_setup.js
index b7ac7449045..8c0346e6198 100644
--- a/spec/frontend/vue_compat_test_setup.js
+++ b/spec/frontend/vue_compat_test_setup.js
@@ -96,15 +96,14 @@ if (global.document) {
$slots: slots = {},
$scopedSlots: scopedSlots = {},
$parent: parent,
- $vnode: { children },
+ $vnode: vnode,
} = this;
- const hasDefaultSlot = 'default' in slots || 'default' in scopedSlots;
- const isTheOnlyChild = parent && parent.$.subTree.children === children;
-
+ const hasStaticDefaultSlot = 'default' in slots && !('default' in scopedSlots);
+ const isTheOnlyChild = parent?.$.subTree === vnode;
// this condition should be altered when https://github.com/vuejs/vue-test-utils/pull/2068 is merged
// and our codebase will be updated to include it (@vue/test-utils@1.3.6 I assume)
- const shouldRenderAllSlots = !hasDefaultSlot && isTheOnlyChild;
+ const shouldRenderAllSlots = !hasStaticDefaultSlot && isTheOnlyChild;
const renderSlotByName = (slotName) => {
const slot = scopedSlots[slotName] || slots[slotName];
diff --git a/spec/models/service_desk/custom_email_verification_spec.rb b/spec/models/service_desk/custom_email_verification_spec.rb
index f0a6028f21d..f114367cfbf 100644
--- a/spec/models/service_desk/custom_email_verification_spec.rb
+++ b/spec/models/service_desk/custom_email_verification_spec.rb
@@ -3,89 +3,149 @@
require 'spec_helper'
RSpec.describe ServiceDesk::CustomEmailVerification, feature_category: :service_desk do
- let(:user) { build_stubbed(:user) }
- let(:project) { build_stubbed(:project) }
- let(:verification) { build_stubbed(:service_desk_custom_email_verification, project: project) }
- let(:token) { 'XXXXXXXXXXXX' }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ let(:generate_token_pattern) { /\A\p{Alnum}{12}\z/ }
describe '.generate_token' do
it 'matches expected output' do
- expect(described_class.generate_token).to match(/\A\p{Alnum}{12}\z/)
+ expect(described_class.generate_token).to match(generate_token_pattern)
end
end
describe 'validations' do
+ subject { build(:service_desk_custom_email_verification, project: project) }
+
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:state) }
+
+ context 'when status is :started' do
+ before do
+ subject.mark_as_started!(user)
+ end
+
+ it { is_expected.to validate_presence_of(:token) }
+ it { is_expected.to validate_length_of(:token).is_equal_to(12) }
+
+ it 'matches .generate_token pattern' do
+ expect(subject.token).to match(generate_token_pattern)
+ end
+
+ it { is_expected.to validate_presence_of(:triggerer) }
+ it { is_expected.to validate_presence_of(:triggered_at) }
+ it { is_expected.to validate_absence_of(:error) }
+ end
+
+ context 'when status is :finished' do
+ before do
+ subject.mark_as_started!(user)
+ subject.mark_as_finished!
+ end
+
+ it { is_expected.to validate_absence_of(:token) }
+ it { is_expected.to validate_absence_of(:error) }
+ end
+
+ context 'when status is :failed' do
+ before do
+ subject.mark_as_started!(user)
+ subject.mark_as_failed!(:smtp_host_issue)
+ end
+
+ it { is_expected.to validate_presence_of(:error) }
+ it { is_expected.to validate_absence_of(:token) }
+ end
end
- describe '#accepted_until' do
- context 'when no custom email is set up' do
- it 'returns nil' do
- expect(subject.accepted_until).to be_nil
+ describe 'status state machine' do
+ subject { build(:service_desk_custom_email_verification, project: project) }
+
+ describe 'transitioning to started' do
+ it 'records the started at time and generates token' do
+ subject.mark_as_started!(user)
+
+ is_expected.to be_started
+ expect(subject.token).to be_present
+ expect(subject.triggered_at).to be_present
+ expect(subject.triggerer).to eq(user)
end
end
- context 'when custom email is set up' do
- subject { verification.accepted_until }
+ describe 'transitioning to finished' do
+ it 'removes the generated token' do
+ subject.mark_as_started!(user)
+ subject.mark_as_finished!
- it { is_expected.to be_nil }
+ is_expected.to be_finished
+ expect(subject.token).not_to be_present
+ end
+ end
- context 'when verification process started' do
- let(:triggered_at) { 2.minutes.ago }
+ describe 'transitioning to failed' do
+ let(:error) { :smtp_host_issue }
- before do
- verification.assign_attributes(
- state: "running",
- triggered_at: triggered_at,
- triggerer: user,
- token: token
- )
- end
+ it 'removes the generated token' do
+ subject.mark_as_started!(user)
+ subject.mark_as_failed!(error)
- it { is_expected.to eq(described_class::TIMEFRAME.since(triggered_at)) }
+ is_expected.to be_failed
+ expect(subject.token).not_to be_present
+ expect(subject.error).to eq(error.to_s)
end
end
end
- describe '#in_timeframe?' do
- context 'when no custom email is set up' do
- it 'returns false' do
- expect(subject).not_to be_in_timeframe
- end
+ describe '#accepted_until' do
+ it 'returns nil' do
+ expect(subject.accepted_until).to be_nil
end
- context 'when custom email is set up' do
- it { is_expected.not_to be_in_timeframe }
+ context 'when state is :started and successfully transitioned' do
+ let(:triggered_at) { 2.minutes.ago }
- context 'when verification process started' do
- let(:triggered_at) { 1.second.ago }
+ before do
+ subject.project = project
+ subject.mark_as_started!(user)
+ end
- before do
- subject.assign_attributes(
- state: "running",
- triggered_at: triggered_at,
- triggerer: user,
- token: token
- )
+ it 'returns correct timeframe end time' do
+ expect(subject.accepted_until).to eq(described_class::TIMEFRAME.since(subject.triggered_at))
+ end
+
+ context 'when triggered_at is not set' do
+ it 'returns nil' do
+ subject.triggered_at = nil
+ expect(subject.accepted_until).to be nil
end
+ end
+ end
+ end
- it { is_expected.to be_in_timeframe }
+ describe '#in_timeframe?' do
+ it { is_expected.not_to be_in_timeframe }
- context 'and timeframe was missed' do
- let(:triggered_at) { (described_class::TIMEFRAME + 1).ago }
+ context 'when state is :started and successfully transitioned' do
+ before do
+ subject.project = project
+ subject.mark_as_started!(user)
+ end
- before do
- subject.triggered_at = triggered_at
- end
+ it { is_expected.to be_in_timeframe }
- it { is_expected.not_to be_in_timeframe }
+ context 'and timeframe was missed' do
+ before do
+ subject.triggered_at = (described_class::TIMEFRAME + 1).ago
end
+
+ it { is_expected.not_to be_in_timeframe }
end
end
end
describe 'encrypted #token' do
+ let(:token) { 'XXXXXXXXXXXX' }
+
subject { build_stubbed(:service_desk_custom_email_verification, token: token) }
it 'saves and retrieves the encrypted token and iv correctly' do
@@ -101,9 +161,10 @@ RSpec.describe ServiceDesk::CustomEmailVerification, feature_category: :service_
it { is_expected.to belong_to(:triggerer) }
it 'can access service desk setting from project' do
- setting = build_stubbed(:service_desk_setting, project: project)
+ subject.project = project
+ setting = build_stubbed(:service_desk_setting, project: subject.project)
- expect(verification.service_desk_setting).to eq(setting)
+ expect(subject.service_desk_setting).to eq(setting)
end
end
end
diff --git a/spec/requests/api/npm_project_packages_spec.rb b/spec/requests/api/npm_project_packages_spec.rb
index f621af5d968..1f5ebc80824 100644
--- a/spec/requests/api/npm_project_packages_spec.rb
+++ b/spec/requests/api/npm_project_packages_spec.rb
@@ -214,6 +214,14 @@ RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do
it_behaves_like 'not a package tracking event'
end
end
+
+ context 'invalid package attachment data' do
+ let(:package_name) { "@#{group.path}/my_package_name" }
+ let(:params) { upload_params(package_name: package_name, file: 'npm/payload_with_empty_attachment.json') }
+
+ it_behaves_like 'handling invalid record with 400 error'
+ it_behaves_like 'not a package tracking event'
+ end
end
context 'valid package params' do
diff --git a/spec/services/packages/npm/create_package_service_spec.rb b/spec/services/packages/npm/create_package_service_spec.rb
index b2ae89f0f9d..a12d86412d8 100644
--- a/spec/services/packages/npm/create_package_service_spec.rb
+++ b/spec/services/packages/npm/create_package_service_spec.rb
@@ -294,6 +294,13 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
end
end
+ context 'with empty attachment data' do
+ let(:params) { super().merge({ _attachments: { "#{package_name}-#{version}.tgz" => { data: '' } } }) }
+
+ it { expect(subject[:http_status]).to eq 400 }
+ it { expect(subject[:message]).to eq 'Attachment data is empty.' }
+ end
+
it 'obtains a lease to create a new package' do
expect_to_obtain_exclusive_lease(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT)