mattermost-mobile/app/screens/invite/actions.test.ts
Daniel Espino García d76791ebe6
Add guest invites (#9288)
* Add guest invites

* Fix tests and address copilot feedback

* Fix test

* Add magic link changes
2025-12-11 17:21:18 +01:00

900 lines
31 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable max-lines */
import {createIntl} from 'react-intl';
import {addMembersToChannel} from '@actions/remote/channel';
import {addUsersToTeam, getTeamMembersByIds, sendEmailInvitesToTeam, sendGuestEmailInvitesToTeam} from '@actions/remote/team';
import {ServerErrors} from '@constants';
import {DEFAULT_LOCALE, getTranslations} from '@i18n';
import TestHelper from '@test/test_helper';
import {sendGuestInvites, sendMembersInvites} from './actions';
jest.mock('@actions/remote/channel');
jest.mock('@actions/remote/team');
const mockAddMembersToChannel = jest.mocked(addMembersToChannel);
const mockAddUsersToTeam = jest.mocked(addUsersToTeam);
const mockGetTeamMembersByIds = jest.mocked(getTeamMembersByIds);
const mockSendEmailInvitesToTeam = jest.mocked(sendEmailInvitesToTeam);
const mockSendGuestEmailInvitesToTeam = jest.mocked(sendGuestEmailInvitesToTeam);
const translations = getTranslations(DEFAULT_LOCALE);
const intl = createIntl({locale: DEFAULT_LOCALE, messages: translations});
describe('actions', () => {
const serverUrl = 'https://test.server.com';
const teamId = 'team-1';
const teamDisplayName = 'Test Team';
const formatMessage = intl.formatMessage;
describe('sendMembersInvites', () => {
it('should successfully invite users to team', async () => {
const userId1 = 'user-1';
const userId2 = 'user-2';
const user1 = TestHelper.fakeUser({id: userId1, roles: 'system_user'});
const user2 = TestHelper.fakeUser({id: userId2, roles: 'system_user'});
const selectedIds = {
[userId1]: user1,
[userId2]: user2,
};
mockGetTeamMembersByIds.mockResolvedValue({
members: [],
error: undefined,
});
mockAddUsersToTeam.mockResolvedValue({
members: [
{member: TestHelper.fakeTeamMember(userId1, 'team-1'), user_id: userId1, error: undefined},
{member: TestHelper.fakeTeamMember(userId2, 'team-1'), user_id: userId2, error: undefined},
],
error: undefined,
});
const result = await sendMembersInvites(
serverUrl,
teamId,
selectedIds,
false,
teamDisplayName,
formatMessage,
);
expect(result.error).toBe(false);
expect(result.sent).toHaveLength(2);
expect(result.notSent).toHaveLength(0);
expect(result.sent[0].userId).toBe(userId1);
expect(result.sent[1].userId).toBe(userId2);
expect(mockGetTeamMembersByIds).toHaveBeenCalledWith(serverUrl, teamId, [userId1, userId2]);
expect(mockAddUsersToTeam).toHaveBeenCalledWith(serverUrl, teamId, [userId1, userId2]);
});
it('should successfully invite users via email', async () => {
const email1 = 'user1@example.com';
const email2 = 'user2@example.com';
const selectedIds = {
[email1]: email1,
[email2]: email2,
};
mockSendEmailInvitesToTeam.mockResolvedValue({
members: [
{email: email1, error: undefined},
{email: email2, error: undefined},
],
error: undefined,
});
const result = await sendMembersInvites(
serverUrl,
teamId,
selectedIds,
false,
teamDisplayName,
formatMessage,
);
expect(result.error).toBe(false);
expect(result.sent).toHaveLength(2);
expect(result.notSent).toHaveLength(0);
expect(result.sent[0].userId).toBe(email1);
expect(result.sent[1].userId).toBe(email2);
expect(mockSendEmailInvitesToTeam).toHaveBeenCalledWith(serverUrl, teamId, [email1, email2]);
});
it('should handle mixed users and emails', async () => {
const userId = 'user-1';
const email = 'user@example.com';
const user = TestHelper.fakeUser({id: userId, roles: 'system_user'});
const selectedIds = {
[userId]: user,
[email]: email,
};
mockGetTeamMembersByIds.mockResolvedValue({
members: [],
error: undefined,
});
mockAddUsersToTeam.mockResolvedValue({
members: [{member: {...TestHelper.fakeTeamMember(userId, 'team-1')}, user_id: userId, error: undefined}],
error: undefined,
});
mockSendEmailInvitesToTeam.mockResolvedValue({
members: [{email, error: undefined}],
error: undefined,
});
const result = await sendMembersInvites(
serverUrl,
teamId,
selectedIds,
false,
teamDisplayName,
formatMessage,
);
expect(result.error).toBe(false);
expect(result.sent).toHaveLength(2);
expect(result.notSent).toHaveLength(0);
});
it('should not invite guest users', async () => {
const userId = 'user-1';
const user = {id: userId, roles: 'system_guest'} as UserProfile;
const selectedIds = {[userId]: user};
mockGetTeamMembersByIds.mockResolvedValue({
members: [],
error: undefined,
});
const result = await sendMembersInvites(
serverUrl,
teamId,
selectedIds,
false,
teamDisplayName,
formatMessage,
);
expect(result.error).toBe(false);
expect(result.sent).toHaveLength(0);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].userId).toBe(userId);
expect(result.notSent[0].reason).toBe('Contact your admin to make this guest a full member');
expect(mockAddUsersToTeam).not.toHaveBeenCalled();
});
it('should not invite users who are already team members', async () => {
const userId = 'user-1';
const user = {id: userId, roles: 'system_user'} as UserProfile;
const selectedIds = {[userId]: user};
mockGetTeamMembersByIds.mockResolvedValue({
members: [TestHelper.fakeTeamMember(userId, 'team-1')],
error: undefined,
});
const result = await sendMembersInvites(
serverUrl,
teamId,
selectedIds,
false,
teamDisplayName,
formatMessage,
);
expect(result.error).toBe(false);
expect(result.sent).toHaveLength(0);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].userId).toBe(userId);
expect(mockAddUsersToTeam).not.toHaveBeenCalled();
});
it('should handle errors from getTeamMembersByIds', async () => {
const userId = 'user-1';
const user = {id: userId, roles: 'system_user'} as UserProfile;
const selectedIds = {[userId]: user};
mockGetTeamMembersByIds.mockResolvedValue({
members: [],
error: {message: 'Database error'} as any,
});
const result = await sendMembersInvites(
serverUrl,
teamId,
selectedIds,
false,
teamDisplayName,
formatMessage,
);
expect(result.error).toBe(true);
expect(result.sent).toHaveLength(0);
expect(result.notSent).toHaveLength(0);
});
it('should handle errors from addUsersToTeam', async () => {
const userId = 'user-1';
const user = {id: userId, roles: 'system_user'} as UserProfile;
const selectedIds = {[userId]: user};
mockGetTeamMembersByIds.mockResolvedValue({
members: [],
error: undefined,
});
mockAddUsersToTeam.mockResolvedValue({
members: [],
error: {message: 'Failed to add users'} as any,
});
const result = await sendMembersInvites(
serverUrl,
teamId,
selectedIds,
false,
teamDisplayName,
formatMessage,
);
expect(result.error).toBe(true);
expect(result.sent).toHaveLength(0);
expect(result.notSent).toHaveLength(0);
});
it('should handle individual user errors from addUsersToTeam', async () => {
const userId1 = 'user-1';
const userId2 = 'user-2';
const user1 = {id: userId1, roles: 'system_user'} as UserProfile;
const user2 = {id: userId2, roles: 'system_user'} as UserProfile;
const selectedIds = {
[userId1]: user1,
[userId2]: user2,
};
mockGetTeamMembersByIds.mockResolvedValue({
members: [],
error: undefined,
});
mockAddUsersToTeam.mockResolvedValue({
members: [
{member: TestHelper.fakeTeamMember(userId1, 'team-1'), user_id: userId1, error: {message: 'User not found'} as any},
{member: TestHelper.fakeTeamMember(userId2, 'team-1'), user_id: userId2, error: undefined},
],
error: undefined,
});
const result = await sendMembersInvites(
serverUrl,
teamId,
selectedIds,
false,
teamDisplayName,
formatMessage,
);
expect(result.error).toBe(false);
expect(result.sent).toHaveLength(1);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].userId).toBe(userId1);
expect(result.notSent[0].reason).toBe('User not found');
expect(result.sent[0].userId).toBe(userId2);
});
it('should handle errors from sendEmailInvitesToTeam', async () => {
const email = 'user@example.com';
const selectedIds = {[email]: email};
mockSendEmailInvitesToTeam.mockResolvedValue({
members: [],
error: {message: 'SMTP error'} as any,
});
const result = await sendMembersInvites(
serverUrl,
teamId,
selectedIds,
false,
teamDisplayName,
formatMessage,
);
expect(result.error).toBe(true);
expect(result.sent).toHaveLength(0);
expect(result.notSent).toHaveLength(0);
});
it('should handle individual email errors', async () => {
const email1 = 'user1@example.com';
const email2 = 'user2@example.com';
const selectedIds = {
[email1]: email1,
[email2]: email2,
};
mockSendEmailInvitesToTeam.mockResolvedValue({
members: [
{email: email1, error: {message: 'Invalid email'} as any},
{email: email2, error: undefined},
],
error: undefined,
});
const result = await sendMembersInvites(
serverUrl,
teamId,
selectedIds,
false,
teamDisplayName,
formatMessage,
);
expect(result.error).toBe(false);
expect(result.sent).toHaveLength(1);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].userId).toBe(email1);
expect(result.notSent[0].reason).toBe('Invalid email');
expect(result.sent[0].userId).toBe(email2);
});
it('should show SMTP configuration error for admin when email fails with SEND_EMAIL_WITH_DEFAULTS_ERROR', async () => {
const email = 'user@example.com';
const selectedIds = {[email]: email};
mockSendEmailInvitesToTeam.mockResolvedValue({
members: [
{
email,
error: {
message: 'SMTP error',
server_error_id: ServerErrors.SEND_EMAIL_WITH_DEFAULTS_ERROR,
} as any,
},
],
error: undefined,
});
const result = await sendMembersInvites(
serverUrl,
teamId,
selectedIds,
true, // isAdmin
teamDisplayName,
formatMessage,
);
expect(result.error).toBe(false);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].reason).toContain('SMTP is not configured');
expect(result.notSent[0].reason).toBe('SMTP is not configured in System Console');
});
it('should show regular error message for non-admin when email fails with SEND_EMAIL_WITH_DEFAULTS_ERROR', async () => {
const email = 'user@example.com';
const selectedIds = {[email]: email};
mockSendEmailInvitesToTeam.mockResolvedValue({
members: [
{
email,
error: {
message: 'SMTP error',
server_error_id: ServerErrors.SEND_EMAIL_WITH_DEFAULTS_ERROR,
} as any,
},
],
error: undefined,
});
const result = await sendMembersInvites(
serverUrl,
teamId,
selectedIds,
false, // not admin
teamDisplayName,
formatMessage,
);
expect(result.error).toBe(false);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].reason).toBe('SMTP error');
});
});
describe('sendGuestInvites', () => {
const options = {
inviteAsGuest: true,
includeCustomMessage: false,
customMessage: '',
selectedChannels: ['channel-1', 'channel-2'],
guestMagicLink: false,
};
it('should successfully invite guest users', async () => {
const userId = 'user-1';
const user = {id: userId, roles: 'system_guest'} as UserProfile;
const selectedIds = {[userId]: user};
mockAddUsersToTeam.mockResolvedValue({
members: [{member: TestHelper.fakeTeamMember(userId, 'team-1'), user_id: userId, error: undefined}],
error: undefined,
});
mockAddMembersToChannel.mockResolvedValue({
channelMemberships: [TestHelper.fakeChannelMember({user_id: userId, channel_id: 'channel-1'})],
error: undefined,
});
const result = await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
options,
formatMessage,
);
expect(result.sent).toHaveLength(1);
expect(result.notSent).toHaveLength(0);
expect(result.sent[0].userId).toBe(userId);
});
it('should successfully invite guests via email', async () => {
const email = 'guest@example.com';
const selectedIds = {[email]: email};
mockSendGuestEmailInvitesToTeam.mockResolvedValue({
members: [{email, error: undefined}],
error: undefined,
});
const result = await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
options,
formatMessage,
);
expect(result.sent).toHaveLength(1);
expect(result.notSent).toHaveLength(0);
expect(result.sent[0].email).toBe(email);
});
it('should handle mixed users and emails for guest invites', async () => {
const userId = 'user-1';
const email = 'guest@example.com';
const user = {id: userId, roles: 'system_guest'} as UserProfile;
const selectedIds = {
[userId]: user,
[email]: email,
};
mockAddUsersToTeam.mockResolvedValue({
members: [{member: TestHelper.fakeTeamMember(userId, 'team-1'), user_id: userId, error: undefined}],
error: undefined,
});
mockAddMembersToChannel.mockResolvedValue({
channelMemberships: [TestHelper.fakeChannelMember({user_id: userId, channel_id: 'channel-1'})],
error: undefined,
});
mockSendGuestEmailInvitesToTeam.mockResolvedValue({
members: [{email, error: undefined}],
error: undefined,
});
const result = await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
options,
formatMessage,
);
expect(result.sent).toHaveLength(2);
expect(result.notSent).toHaveLength(0);
});
it('should not invite non-guest users as guests', async () => {
const userId = 'user-1';
const user = TestHelper.fakeUser({id: userId, roles: 'system_user'});
const selectedIds = {[userId]: user};
const result = await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
options,
formatMessage,
);
expect(result.sent).toHaveLength(0);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].userId).toBe(userId);
expect(result.notSent[0].reason).toBe('This person is already a member of the workspace. Invite them as a member instead of a guest.');
});
it('should handle errors from addUsersToTeam for guest invites', async () => {
const userId = 'user-1';
const user = TestHelper.fakeUser({id: userId, roles: 'system_guest'});
const selectedIds = {[userId]: user};
mockAddUsersToTeam.mockResolvedValue({
members: [],
error: {message: 'Failed to add user'} as any,
});
const result = await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
options,
formatMessage,
);
expect(result.sent).toHaveLength(0);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].userId).toBe(userId);
expect(result.notSent[0].reason).toBe('Failed to add user');
});
it('should handle errors when adding guest to channels', async () => {
const userId = 'user-1';
const user = {id: userId, roles: 'system_guest'} as UserProfile;
const selectedIds = {[userId]: user};
mockAddUsersToTeam.mockResolvedValue({
members: [{member: TestHelper.fakeTeamMember(userId, 'team-1'), user_id: userId, error: undefined}],
error: undefined,
});
mockAddMembersToChannel.mockResolvedValue({
channelMemberships: [],
error: undefined,
});
const result = await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
options,
formatMessage,
);
expect(result.sent).toHaveLength(0);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].userId).toBe(userId);
expect(result.notSent[0].reason).toBe('Unable to add user to 2 channels');
});
it('should handle errors from addMembersToChannel', async () => {
const userId = 'user-1';
const user = {id: userId, roles: 'system_guest'} as UserProfile;
const selectedIds = {[userId]: user};
mockAddUsersToTeam.mockResolvedValue({
members: [{member: TestHelper.fakeTeamMember(userId, 'team-1'), user_id: userId, error: undefined}],
error: undefined,
});
mockAddMembersToChannel.mockResolvedValue({
channelMemberships: [],
error: {message: 'Channel error'} as any,
});
const result = await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
options,
formatMessage,
);
expect(result.sent).toHaveLength(0);
expect(result.notSent.length).toBe(1);
expect(result.notSent[0].userId).toBe(userId);
expect(result.notSent[0].reason).toBe('Unable to add user to 2 channels');
});
it('should handle partial channel failures (some channels succeed, some fail)', async () => {
const userId = 'user-1';
const user = {id: userId, roles: 'system_guest'} as UserProfile;
const selectedIds = {[userId]: user};
mockAddUsersToTeam.mockResolvedValue({
members: [{member: TestHelper.fakeTeamMember(userId, 'team-1'), user_id: userId, error: undefined}],
error: undefined,
});
// First channel succeeds, second fails with empty memberships
mockAddMembersToChannel.
mockResolvedValueOnce({
channelMemberships: [TestHelper.fakeChannelMember({user_id: userId, channel_id: 'channel-1'})],
error: undefined,
}).
mockResolvedValueOnce({
channelMemberships: [],
error: undefined,
});
const result = await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
options,
formatMessage,
);
expect(result.sent).toHaveLength(0);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].userId).toBe(userId);
expect(result.notSent[0].reason).toBe('Unable to add user to 1 channel');
});
it('should successfully add guest to multiple channels', async () => {
const userId = 'user-1';
const user = {id: userId, roles: 'system_guest'} as UserProfile;
const selectedIds = {[userId]: user};
mockAddUsersToTeam.mockResolvedValue({
members: [{member: TestHelper.fakeTeamMember(userId, 'team-1'), user_id: userId, error: undefined}],
error: undefined,
});
mockAddMembersToChannel.
mockResolvedValueOnce({
channelMemberships: [TestHelper.fakeChannelMember({user_id: userId, channel_id: 'channel-1'})],
error: undefined,
}).
mockResolvedValueOnce({
channelMemberships: [TestHelper.fakeChannelMember({user_id: userId, channel_id: 'channel-2'})],
error: undefined,
});
const result = await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
options,
formatMessage,
);
expect(result.sent).toHaveLength(1);
expect(result.notSent).toHaveLength(0);
expect(result.sent[0].userId).toBe(userId);
expect(result.sent[0].reason).toBe('This guest has been added to the team and 2 channels.');
});
it('should handle errors from sendGuestEmailInvitesToTeam', async () => {
const email = 'guest@example.com';
const selectedIds = {[email]: email};
mockSendGuestEmailInvitesToTeam.mockResolvedValue({
members: [],
error: {message: 'Email error'} as any,
});
const result = await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
options,
formatMessage,
);
expect(result.sent).toHaveLength(0);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].userId).toBe(email);
expect(result.notSent[0].reason).toBe('Email error');
});
it('should handle individual email errors from sendGuestEmailInvitesToTeam', async () => {
const email1 = 'guest1@example.com';
const email2 = 'guest2@example.com';
const selectedIds = {
[email1]: email1,
[email2]: email2,
};
mockSendGuestEmailInvitesToTeam.mockResolvedValue({
members: [
{email: email1, error: {message: 'Invalid email'} as any},
{email: email2, error: undefined},
],
error: undefined,
});
const result = await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
options,
formatMessage,
);
expect(result.sent).toHaveLength(1);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].email).toBe(email1);
expect(result.notSent[0].reason).toBe('Invalid email');
expect(result.sent[0].email).toBe(email2);
});
it('should include custom message when sending guest email invites', async () => {
const email = 'guest@example.com';
const selectedIds = {[email]: email};
const customOptions = {
...options,
includeCustomMessage: true,
customMessage: 'Welcome to the team!',
};
mockSendGuestEmailInvitesToTeam.mockResolvedValue({
members: [{email, error: undefined}],
error: undefined,
});
await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
customOptions,
formatMessage,
);
expect(mockSendGuestEmailInvitesToTeam).toHaveBeenCalledWith(
serverUrl,
teamId,
[email],
options.selectedChannels,
'Welcome to the team!',
false,
);
});
it('should not include custom message when includeCustomMessage is false', async () => {
const email = 'guest@example.com';
const selectedIds = {[email]: email};
const customOptions = {
...options,
includeCustomMessage: false,
customMessage: 'Welcome to the team!',
};
mockSendGuestEmailInvitesToTeam.mockResolvedValue({
members: [{email, error: undefined}],
error: undefined,
});
await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
customOptions,
formatMessage,
);
expect(mockSendGuestEmailInvitesToTeam).toHaveBeenCalledWith(
serverUrl,
teamId,
[email],
options.selectedChannels,
'',
false,
);
});
it('should send guest email invites with guest magic link', async () => {
const email = 'guest@example.com';
const selectedIds = {[email]: email};
const customOptions = {
...options,
guestMagicLink: true,
};
mockSendGuestEmailInvitesToTeam.mockResolvedValue({
members: [{email, error: undefined}],
error: undefined,
});
await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
customOptions,
formatMessage,
);
expect(mockSendGuestEmailInvitesToTeam).toHaveBeenCalledWith(
serverUrl,
teamId,
[email],
options.selectedChannels,
'',
true,
);
});
it('should handle empty members response from sendGuestEmailInvitesToTeam', async () => {
const email = 'guest@example.com';
const selectedIds = {[email]: email};
mockSendGuestEmailInvitesToTeam.mockResolvedValue({
members: [],
error: undefined,
});
const result = await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
options,
formatMessage,
);
expect(result.sent).toHaveLength(0);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].email).toBe(email);
expect(result.notSent[0].reason).toBe('Failed to send email invitation');
});
it('should handle user error from addUsersToTeam member response', async () => {
const userId = 'user-1';
const user = TestHelper.fakeUser({id: userId, roles: 'system_guest'});
const selectedIds = {[userId]: user};
mockAddUsersToTeam.mockResolvedValue({
members: [{member: TestHelper.fakeTeamMember(userId, 'team-1'), user_id: userId, error: {message: 'User error'} as any}],
error: undefined,
});
const result = await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
options,
formatMessage,
);
expect(result.sent).toHaveLength(0);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].userId).toBe(userId);
expect(result.notSent[0].reason).toBe('User error');
});
it('should handle empty members array from addUsersToTeam', async () => {
const userId = 'user-1';
const user = {id: userId, roles: 'system_guest'} as UserProfile;
const selectedIds = {[userId]: user};
mockAddUsersToTeam.mockResolvedValue({
members: [],
error: undefined,
});
const result = await sendGuestInvites(
serverUrl,
teamId,
selectedIds,
options,
formatMessage,
);
expect(result.sent).toHaveLength(0);
expect(result.notSent).toHaveLength(1);
expect(result.notSent[0].userId).toBe(userId);
expect(result.notSent[0].reason).toBe('Unknown error');
});
});
});