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
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-07-16 00:09:09 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-07-16 00:09:09 +0300
commitd1377e82d9bccd6b49c08aa67f6776b7981295ae (patch)
tree0a02a1b531d6e8097efc11468303bffbd0f18af5 /spec/frontend/invite_members
parentd811b6d8f61b45cd12f94251abff9102b8cefc19 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/invite_members')
-rw-r--r--spec/frontend/invite_members/components/invite_members_modal_spec.js295
-rw-r--r--spec/frontend/invite_members/components/members_token_select_spec.js15
-rw-r--r--spec/frontend/invite_members/mock_data/api_responses.js74
-rw-r--r--spec/frontend/invite_members/utils/response_message_parser_spec.js36
4 files changed, 344 insertions, 76 deletions
diff --git a/spec/frontend/invite_members/components/invite_members_modal_spec.js b/spec/frontend/invite_members/components/invite_members_modal_spec.js
index eabbea84234..b828b5d8a04 100644
--- a/spec/frontend/invite_members/components/invite_members_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js
@@ -1,11 +1,27 @@
-import { GlDropdown, GlDropdownItem, GlDatepicker, GlSprintf, GlLink, GlModal } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import {
+ GlDropdown,
+ GlDropdownItem,
+ GlDatepicker,
+ GlFormGroup,
+ GlSprintf,
+ GlLink,
+ GlModal,
+} from '@gitlab/ui';
+import MockAdapter from 'axios-mock-adapter';
import { stubComponent } from 'helpers/stub_component';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import InviteMembersModal from '~/invite_members/components/invite_members_modal.vue';
+import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
import { INVITE_MEMBERS_IN_COMMENT } from '~/invite_members/constants';
+import axios from '~/lib/utils/axios_utils';
+import httpStatus from '~/lib/utils/http_status';
+import { apiPaths, membersApiResponse, invitationsApiResponse } from '../mock_data/api_responses';
+
+let wrapper;
+let mock;
jest.mock('~/experimentation/experiment_tracking');
@@ -26,10 +42,16 @@ const user3 = {
username: 'one_2',
avatar_url: '',
};
+const user4 = {
+ id: 'user-defined-token',
+ name: 'email4@example.com',
+ username: 'one_4',
+ avatar_url: '',
+};
const sharedGroup = { id: '981' };
const createComponent = (data = {}, props = {}) => {
- return shallowMount(InviteMembersModal, {
+ wrapper = shallowMountExtended(InviteMembersModal, {
propsData: {
id,
name,
@@ -51,46 +73,56 @@ const createComponent = (data = {}, props = {}) => {
GlDropdown: true,
GlDropdownItem: true,
GlSprintf,
+ GlFormGroup: stubComponent(GlFormGroup, {
+ props: ['state', 'invalidFeedback'],
+ }),
},
});
};
const createInviteMembersToProjectWrapper = () => {
- return createComponent({ inviteeType: 'members' }, { isProject: true });
+ createComponent({ inviteeType: 'members' }, { isProject: true });
};
const createInviteMembersToGroupWrapper = () => {
- return createComponent({ inviteeType: 'members' }, { isProject: false });
+ createComponent({ inviteeType: 'members' }, { isProject: false });
};
const createInviteGroupToProjectWrapper = () => {
- return createComponent({ inviteeType: 'group' }, { isProject: true });
+ createComponent({ inviteeType: 'group' }, { isProject: true });
};
const createInviteGroupToGroupWrapper = () => {
- return createComponent({ inviteeType: 'group' }, { isProject: false });
+ createComponent({ inviteeType: 'group' }, { isProject: false });
};
-describe('InviteMembersModal', () => {
- let wrapper;
+beforeEach(() => {
+ gon.api_version = 'v4';
+ mock = new MockAdapter(axios);
+});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
+afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ mock.restore();
+});
+describe('InviteMembersModal', () => {
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownItems = () => findDropdown().findAllComponents(GlDropdownItem);
const findDatepicker = () => wrapper.findComponent(GlDatepicker);
const findLink = () => wrapper.findComponent(GlLink);
const findIntroText = () => wrapper.find({ ref: 'introText' }).text();
- const findCancelButton = () => wrapper.findComponent({ ref: 'cancelButton' });
- const findInviteButton = () => wrapper.findComponent({ ref: 'inviteButton' });
+ const findCancelButton = () => wrapper.findByTestId('cancel-button');
+ const findInviteButton = () => wrapper.findByTestId('invite-button');
const clickInviteButton = () => findInviteButton().vm.$emit('click');
+ const findMembersFormGroup = () => wrapper.findByTestId('members-form-group');
+ const membersFormGroupInvalidFeedback = () => findMembersFormGroup().props('invalidFeedback');
+ const findMembersSelect = () => wrapper.findComponent(MembersTokenSelect);
describe('rendering the modal', () => {
beforeEach(() => {
- wrapper = createComponent();
+ createComponent();
});
it('renders the modal with the correct title', () => {
@@ -132,7 +164,7 @@ describe('InviteMembersModal', () => {
describe('when inviting to a project', () => {
describe('when inviting members', () => {
it('includes the correct invitee, type, and formatted name', () => {
- wrapper = createInviteMembersToProjectWrapper();
+ createInviteMembersToProjectWrapper();
expect(findIntroText()).toBe("You're inviting members to the test name project.");
});
@@ -140,7 +172,7 @@ describe('InviteMembersModal', () => {
describe('when sharing with a group', () => {
it('includes the correct invitee, type, and formatted name', () => {
- wrapper = createInviteGroupToProjectWrapper();
+ createInviteGroupToProjectWrapper();
expect(findIntroText()).toBe("You're inviting a group to the test name project.");
});
@@ -150,7 +182,7 @@ describe('InviteMembersModal', () => {
describe('when inviting to a group', () => {
describe('when inviting members', () => {
it('includes the correct invitee, type, and formatted name', () => {
- wrapper = createInviteMembersToGroupWrapper();
+ createInviteMembersToGroupWrapper();
expect(findIntroText()).toBe("You're inviting members to the test name group.");
});
@@ -158,7 +190,7 @@ describe('InviteMembersModal', () => {
describe('when sharing with a group', () => {
it('includes the correct invitee, type, and formatted name', () => {
- wrapper = createInviteGroupToGroupWrapper();
+ createInviteGroupToGroupWrapper();
expect(findIntroText()).toBe("You're inviting a group to the test name group.");
});
@@ -167,22 +199,30 @@ describe('InviteMembersModal', () => {
});
describe('submitting the invite form', () => {
- const apiErrorMessage = 'Member already exists';
+ const mockMembersApi = (code, data) => {
+ mock.onPost(apiPaths.GROUPS_MEMBERS).reply(code, data);
+ };
+ const mockInvitationsApi = (code, data) => {
+ mock.onPost(apiPaths.GROUPS_INVITATIONS).reply(code, data);
+ };
+
+ const expectedEmailRestrictedError =
+ "email 'email@example.com' does not match the allowed domains: example1.org";
+ const expectedSyntaxError = 'email contains an invalid email address';
describe('when inviting an existing user to group by user ID', () => {
const postData = {
- user_id: '1',
+ user_id: '1,2',
access_level: defaultAccessLevel,
expires_at: undefined,
invite_source: inviteSource,
format: 'json',
};
- describe('when invites are sent successfully', () => {
+ describe('when member is added successfully', () => {
beforeEach(() => {
- wrapper = createInviteMembersToGroupWrapper();
+ createComponent({ newUsersToInvite: [user1, user2] });
- wrapper.setData({ newUsersToInvite: [user1] });
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'addGroupMembersByUserId').mockResolvedValue({ data: postData });
jest.spyOn(wrapper.vm, 'showToastMessageSuccess');
@@ -190,54 +230,102 @@ describe('InviteMembersModal', () => {
clickInviteButton();
});
- it('calls Api addGroupMembersByUserId with the correct params', () => {
+ it('calls Api addGroupMembersByUserId with the correct params', async () => {
+ await waitForPromises;
+
expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, postData);
});
- it('displays the successful toastMessage', () => {
+ it('displays the successful toastMessage', async () => {
+ await waitForPromises;
+
expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled();
});
});
- describe('when the invite received an api error message', () => {
+ describe('when member is not added successfully', () => {
beforeEach(() => {
- wrapper = createComponent({ newUsersToInvite: [user1] });
+ createInviteMembersToGroupWrapper();
- wrapper.vm.$toast = { show: jest.fn() };
- jest
- .spyOn(Api, 'addGroupMembersByUserId')
- .mockRejectedValue({ response: { data: { message: apiErrorMessage } } });
- jest.spyOn(wrapper.vm, 'showToastMessageError');
+ wrapper.setData({ newUsersToInvite: [user1] });
+ });
+
+ it('displays "Member already exists" api message for http status conflict', async () => {
+ mockMembersApi(httpStatus.CONFLICT, membersApiResponse.MEMBER_ALREADY_EXISTS);
clickInviteButton();
+
+ await waitForPromises();
+
+ expect(membersFormGroupInvalidFeedback()).toBe('Member already exists');
+ expect(findMembersFormGroup().props('state')).toBe(false);
+ expect(findMembersSelect().props('validationState')).toBe(false);
});
- it('displays the apiErrorMessage in the toastMessage', async () => {
+ it('clears the invalid state and message once the list of members to invite is cleared', async () => {
+ mockMembersApi(httpStatus.CONFLICT, membersApiResponse.MEMBER_ALREADY_EXISTS);
+
+ clickInviteButton();
+
await waitForPromises();
- expect(wrapper.vm.showToastMessageError).toHaveBeenCalledWith({
- response: { data: { message: apiErrorMessage } },
- });
+ expect(membersFormGroupInvalidFeedback()).toBe('Member already exists');
+ expect(findMembersFormGroup().props('state')).toBe(false);
+ expect(findMembersSelect().props('validationState')).toBe(false);
+
+ findMembersSelect().vm.$emit('clear');
+
+ await waitForPromises();
+
+ expect(membersFormGroupInvalidFeedback()).toBe('');
+ expect(findMembersFormGroup().props('state')).not.toBe(false);
+ expect(findMembersSelect().props('validationState')).not.toBe(false);
});
- });
- describe('when any invite failed for any other reason', () => {
- beforeEach(() => {
- wrapper = createComponent({ newUsersToInvite: [user1, user2] });
+ it('displays the generic error for http server error', async () => {
+ mockMembersApi(httpStatus.INTERNAL_SERVER_ERROR, 'Request failed with status code 500');
- wrapper.vm.$toast = { show: jest.fn() };
- jest
- .spyOn(Api, 'addGroupMembersByUserId')
- .mockRejectedValue({ response: { data: { success: false } } });
- jest.spyOn(wrapper.vm, 'showToastMessageError');
+ clickInviteButton();
+
+ await waitForPromises();
+
+ expect(membersFormGroupInvalidFeedback()).toBe('Something went wrong');
+ });
+
+ it('displays the restricted user api message for response with bad request', async () => {
+ mockMembersApi(httpStatus.BAD_REQUEST, membersApiResponse.SINGLE_USER_RESTRICTED);
clickInviteButton();
+
+ await waitForPromises();
+
+ expect(membersFormGroupInvalidFeedback()).toBe(expectedEmailRestrictedError);
});
- it('displays the generic error toastMessage', async () => {
+ it('displays the first part of the error when multiple existing users are restricted by email', async () => {
+ mockMembersApi(httpStatus.CREATED, membersApiResponse.MULTIPLE_USERS_RESTRICTED);
+
+ clickInviteButton();
+
await waitForPromises();
- expect(wrapper.vm.showToastMessageError).toHaveBeenCalled();
+ expect(membersFormGroupInvalidFeedback()).toBe(
+ "root: User email 'admin@example.com' does not match the allowed domain of example2.com",
+ );
+ expect(findMembersSelect().props('validationState')).toBe(false);
+ });
+
+ it('displays an access_level error message received for the existing user', async () => {
+ mockMembersApi(httpStatus.BAD_REQUEST, membersApiResponse.SINGLE_USER_ACCESS_LEVEL);
+
+ clickInviteButton();
+
+ await waitForPromises();
+
+ expect(membersFormGroupInvalidFeedback()).toBe(
+ 'should be greater than or equal to Owner inherited membership from group Gitlab Org',
+ );
+ expect(findMembersSelect().props('validationState')).toBe(false);
});
});
});
@@ -253,7 +341,7 @@ describe('InviteMembersModal', () => {
describe('when invites are sent successfully', () => {
beforeEach(() => {
- wrapper = createComponent({ newUsersToInvite: [user3] });
+ createComponent({ newUsersToInvite: [user3] });
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({ data: postData });
@@ -271,23 +359,84 @@ describe('InviteMembersModal', () => {
});
});
- describe('when any invite failed for any reason', () => {
+ describe('when invites are not sent successfully', () => {
beforeEach(() => {
- wrapper = createComponent({ newUsersToInvite: [user1, user2] });
+ createInviteMembersToGroupWrapper();
+
+ wrapper.setData({ newUsersToInvite: [user3] });
+ });
+
+ it('displays the api error for invalid email syntax', async () => {
+ mockInvitationsApi(httpStatus.BAD_REQUEST, invitationsApiResponse.EMAIL_INVALID);
+
+ clickInviteButton();
+
+ await waitForPromises();
+
+ expect(membersFormGroupInvalidFeedback()).toBe(expectedSyntaxError);
+ expect(findMembersSelect().props('validationState')).toBe(false);
+ });
+ it('displays the restricted email error when restricted email is invited', async () => {
+ mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.EMAIL_RESTRICTED);
+
+ clickInviteButton();
+
+ await waitForPromises();
+
+ expect(membersFormGroupInvalidFeedback()).toContain(expectedEmailRestrictedError);
+ expect(findMembersSelect().props('validationState')).toBe(false);
+ });
+
+ it('displays the successful toast message when email has already been invited', async () => {
+ mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.EMAIL_TAKEN);
wrapper.vm.$toast = { show: jest.fn() };
- jest
- .spyOn(Api, 'addGroupMembersByUserId')
- .mockRejectedValue({ response: { data: { success: false } } });
- jest.spyOn(wrapper.vm, 'showToastMessageError');
+ jest.spyOn(wrapper.vm, 'showToastMessageSuccess');
+
+ clickInviteButton();
+
+ await waitForPromises();
+
+ expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled();
+ expect(findMembersSelect().props('validationState')).toBe(null);
+ });
+
+ it('displays the first error message when multiple emails return a restricted error message', async () => {
+ mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.MULTIPLE_EMAIL_RESTRICTED);
clickInviteButton();
+
+ await waitForPromises();
+
+ expect(membersFormGroupInvalidFeedback()).toContain(expectedEmailRestrictedError);
+ expect(findMembersSelect().props('validationState')).toBe(false);
+ });
+
+ it('displays the invalid syntax error for bad request', async () => {
+ mockInvitationsApi(httpStatus.BAD_REQUEST, invitationsApiResponse.ERROR_EMAIL_INVALID);
+
+ clickInviteButton();
+
+ await waitForPromises();
+
+ expect(membersFormGroupInvalidFeedback()).toBe(expectedSyntaxError);
+ expect(findMembersSelect().props('validationState')).toBe(false);
});
+ });
+
+ describe('when multiple emails are invited at the same time', () => {
+ it('displays the invalid syntax error if one of the emails is invalid', async () => {
+ createInviteMembersToGroupWrapper();
+
+ wrapper.setData({ newUsersToInvite: [user3, user4] });
+ mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.ERROR_EMAIL_INVALID);
+
+ clickInviteButton();
- it('displays the generic error toastMessage', async () => {
await waitForPromises();
- expect(wrapper.vm.showToastMessageError).toHaveBeenCalled();
+ expect(membersFormGroupInvalidFeedback()).toBe(expectedSyntaxError);
+ expect(findMembersSelect().props('validationState')).toBe(false);
});
});
});
@@ -305,7 +454,7 @@ describe('InviteMembersModal', () => {
describe('when invites are sent successfully', () => {
beforeEach(() => {
- wrapper = createComponent({ newUsersToInvite: [user1, user3] });
+ createComponent({ newUsersToInvite: [user1, user3] });
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({ data: postData });
@@ -350,24 +499,20 @@ describe('InviteMembersModal', () => {
describe('when any invite failed for any reason', () => {
beforeEach(() => {
- wrapper = createComponent({ newUsersToInvite: [user1, user3] });
+ createInviteMembersToGroupWrapper();
- wrapper.vm.$toast = { show: jest.fn() };
-
- jest
- .spyOn(Api, 'inviteGroupMembersByEmail')
- .mockRejectedValue({ response: { data: { success: false } } });
+ wrapper.setData({ newUsersToInvite: [user1, user3] });
- jest.spyOn(Api, 'addGroupMembersByUserId').mockResolvedValue({ data: postData });
- jest.spyOn(wrapper.vm, 'showToastMessageError');
+ mockInvitationsApi(httpStatus.BAD_REQUEST, invitationsApiResponse.EMAIL_INVALID);
+ mockMembersApi(httpStatus.OK, '200 OK');
clickInviteButton();
});
- it('displays the generic error toastMessage', async () => {
+ it('displays the first error message', async () => {
await waitForPromises();
- expect(wrapper.vm.showToastMessageError).toHaveBeenCalled();
+ expect(membersFormGroupInvalidFeedback()).toBe(expectedSyntaxError);
});
});
});
@@ -382,7 +527,7 @@ describe('InviteMembersModal', () => {
};
beforeEach(() => {
- wrapper = createComponent({ groupToBeSharedWith: sharedGroup });
+ createComponent({ groupToBeSharedWith: sharedGroup });
wrapper.setData({ inviteeType: 'group' });
wrapper.vm.$toast = { show: jest.fn() };
@@ -403,7 +548,7 @@ describe('InviteMembersModal', () => {
describe('when sharing the group fails', () => {
beforeEach(() => {
- wrapper = createComponent({ groupToBeSharedWith: sharedGroup });
+ createComponent({ groupToBeSharedWith: sharedGroup });
wrapper.setData({ inviteeType: 'group' });
wrapper.vm.$toast = { show: jest.fn() };
@@ -412,22 +557,20 @@ describe('InviteMembersModal', () => {
.spyOn(Api, 'groupShareWithGroup')
.mockRejectedValue({ response: { data: { success: false } } });
- jest.spyOn(wrapper.vm, 'showToastMessageError');
-
clickInviteButton();
});
- it('displays the generic error toastMessage', async () => {
+ it('displays the generic error message', async () => {
await waitForPromises();
- expect(wrapper.vm.showToastMessageError).toHaveBeenCalled();
+ expect(membersFormGroupInvalidFeedback()).toBe('Something went wrong');
});
});
});
describe('tracking', () => {
beforeEach(() => {
- wrapper = createComponent({ newUsersToInvite: [user3] });
+ createComponent({ newUsersToInvite: [user3] });
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({});
diff --git a/spec/frontend/invite_members/components/members_token_select_spec.js b/spec/frontend/invite_members/components/members_token_select_spec.js
index f6e79d3607f..12db7e42464 100644
--- a/spec/frontend/invite_members/components/members_token_select_spec.js
+++ b/spec/frontend/invite_members/components/members_token_select_spec.js
@@ -115,6 +115,21 @@ describe('MembersTokenSelect', () => {
expect(wrapper.emitted().input[0][0]).toEqual([user1, user2]);
});
});
+
+ describe('when user is removed', () => {
+ it('emits `clear` event', () => {
+ findTokenSelector().vm.$emit('token-remove', [user1]);
+
+ expect(wrapper.emitted('clear')).toEqual([[]]);
+ });
+
+ it('does not emit `clear` event when there are still tokens selected', () => {
+ findTokenSelector().vm.$emit('input', [user1, user2]);
+ findTokenSelector().vm.$emit('token-remove', [user1]);
+
+ expect(wrapper.emitted('clear')).toBeUndefined();
+ });
+ });
});
describe('when text input is blurred', () => {
diff --git a/spec/frontend/invite_members/mock_data/api_responses.js b/spec/frontend/invite_members/mock_data/api_responses.js
new file mode 100644
index 00000000000..79b56a33708
--- /dev/null
+++ b/spec/frontend/invite_members/mock_data/api_responses.js
@@ -0,0 +1,74 @@
+const INVITATIONS_API_EMAIL_INVALID = {
+ message: { error: 'email contains an invalid email address' },
+};
+
+const INVITATIONS_API_ERROR_EMAIL_INVALID = {
+ error: 'email contains an invalid email address',
+};
+
+const INVITATIONS_API_EMAIL_RESTRICTED = {
+ message: {
+ 'email@example.com':
+ "Invite email 'email@example.com' does not match the allowed domains: example1.org",
+ },
+ status: 'error',
+};
+
+const INVITATIONS_API_MULTIPLE_EMAIL_RESTRICTED = {
+ message: {
+ 'email@example.com':
+ "Invite email email 'email@example.com' does not match the allowed domains: example1.org",
+ 'email4@example.com':
+ "Invite email email 'email4@example.com' does not match the allowed domains: example1.org",
+ },
+ status: 'error',
+};
+
+const INVITATIONS_API_EMAIL_TAKEN = {
+ message: {
+ 'email@example2.com': 'Invite email has already been taken',
+ },
+ status: 'error',
+};
+
+const MEMBERS_API_MEMBER_ALREADY_EXISTS = {
+ message: 'Member already exists',
+};
+
+const MEMBERS_API_SINGLE_USER_RESTRICTED = {
+ message: { user: ["email 'email@example.com' does not match the allowed domains: example1.org"] },
+};
+
+const MEMBERS_API_SINGLE_USER_ACCESS_LEVEL = {
+ message: {
+ access_level: [
+ 'should be greater than or equal to Owner inherited membership from group Gitlab Org',
+ ],
+ },
+};
+
+const MEMBERS_API_MULTIPLE_USERS_RESTRICTED = {
+ message:
+ "root: User email 'admin@example.com' does not match the allowed domain of example2.com and user18: User email 'user18@example.org' does not match the allowed domain of example2.com",
+ status: 'error',
+};
+
+export const apiPaths = {
+ GROUPS_MEMBERS: '/api/v4/groups/1/members',
+ GROUPS_INVITATIONS: '/api/v4/groups/1/invitations',
+};
+
+export const membersApiResponse = {
+ MEMBER_ALREADY_EXISTS: MEMBERS_API_MEMBER_ALREADY_EXISTS,
+ SINGLE_USER_ACCESS_LEVEL: MEMBERS_API_SINGLE_USER_ACCESS_LEVEL,
+ SINGLE_USER_RESTRICTED: MEMBERS_API_SINGLE_USER_RESTRICTED,
+ MULTIPLE_USERS_RESTRICTED: MEMBERS_API_MULTIPLE_USERS_RESTRICTED,
+};
+
+export const invitationsApiResponse = {
+ EMAIL_INVALID: INVITATIONS_API_EMAIL_INVALID,
+ ERROR_EMAIL_INVALID: INVITATIONS_API_ERROR_EMAIL_INVALID,
+ EMAIL_RESTRICTED: INVITATIONS_API_EMAIL_RESTRICTED,
+ MULTIPLE_EMAIL_RESTRICTED: INVITATIONS_API_MULTIPLE_EMAIL_RESTRICTED,
+ EMAIL_TAKEN: INVITATIONS_API_EMAIL_TAKEN,
+};
diff --git a/spec/frontend/invite_members/utils/response_message_parser_spec.js b/spec/frontend/invite_members/utils/response_message_parser_spec.js
new file mode 100644
index 00000000000..3c88b5a2418
--- /dev/null
+++ b/spec/frontend/invite_members/utils/response_message_parser_spec.js
@@ -0,0 +1,36 @@
+import {
+ responseMessageFromSuccess,
+ responseMessageFromError,
+} from '~/invite_members/utils/response_message_parser';
+
+describe('Response message parser', () => {
+ const expectedMessage = 'expected display message';
+
+ describe('parse message from successful response', () => {
+ const exampleKeyedMsg = { 'email@example.com': expectedMessage };
+ const exampleUserMsgMultiple =
+ ' and username1: id not found and username2: email is restricted';
+
+ it.each([
+ [[{ data: { message: expectedMessage } }]],
+ [[{ data: { message: expectedMessage + exampleUserMsgMultiple } }]],
+ [[{ data: { error: expectedMessage } }]],
+ [[{ data: { message: [expectedMessage] } }]],
+ [[{ data: { message: exampleKeyedMsg } }]],
+ ])(`returns "${expectedMessage}" from success response: %j`, (successResponse) => {
+ expect(responseMessageFromSuccess(successResponse)).toBe(expectedMessage);
+ });
+ });
+
+ describe('message from error response', () => {
+ it.each([
+ [{ response: { data: { error: expectedMessage } } }],
+ [{ response: { data: { message: { user: [expectedMessage] } } } }],
+ [{ response: { data: { message: { access_level: [expectedMessage] } } } }],
+ [{ response: { data: { message: { error: expectedMessage } } } }],
+ [{ response: { data: { message: expectedMessage } } }],
+ ])(`returns "${expectedMessage}" from error response: %j`, (errorResponse) => {
+ expect(responseMessageFromError(errorResponse)).toBe(expectedMessage);
+ });
+ });
+});