MM-59854 Fully allow at mentions in message attachment field values and add E2E tests (#28018)
* Add descriptions to test cases for getNeededAtMentionedUsernames * MM-59854 Load users who are at-mentioned in message attachment fields * MM-59854 Add server support for at-mentioning users in message attachment fields * Migrate support/external_commands from JS to TS * MM-59584 Ensure that at-mentions never show in other message attachment fields * MM-59584 Add E2E tests for how we load users based on at mentions * Use new E2E test helpers in other places * Update snapshots
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
3e84d20dbe
Коммит
46cad5c552
@@ -243,13 +243,8 @@ function createChannel(channelType: string, teamId: string, userToAdd: Cypress.U
|
||||
if (userToAdd) {
|
||||
// # Get user profile by email
|
||||
return cy.apiGetUserByEmail(userToAdd.email).then(({user}) => {
|
||||
// # Add user to team
|
||||
cy.externalRequest({
|
||||
user: getAdminAccount(),
|
||||
method: 'post',
|
||||
path: `channels/${channel.id}/members`,
|
||||
data: {user_id: user.id},
|
||||
}).then(() => {
|
||||
// # Add user to channel
|
||||
cy.externalAddUserToChannel(user.id, channel.id).then(() => {
|
||||
// # Explicitly wait to give some time to index before searching
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
return cy.wrap(channel);
|
||||
|
||||
@@ -10,11 +10,7 @@
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
import {getAdminAccount} from '../../../support/env';
|
||||
|
||||
describe('View Members modal', () => {
|
||||
const sysadmin = getAdminAccount();
|
||||
|
||||
it('MM-20164 - Going from a Member to an Admin should update the modal', () => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
cy.apiCreateUser().then(({user: user1}) => {
|
||||
@@ -24,27 +20,19 @@ describe('View Members modal', () => {
|
||||
// # Promote user as a system admin
|
||||
// # Visit default channel and verify members modal
|
||||
cy.apiLogin(user);
|
||||
promoteToSysAdmin(user, sysadmin);
|
||||
cy.externalUpdateUserRoles(user.id, 'system_user system_admin');
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
verifyMemberDropdownAction(true);
|
||||
|
||||
// # Make user a regular member
|
||||
// # Reload and verify members modal
|
||||
demoteToMember(user, sysadmin);
|
||||
cy.externalUpdateUserRoles(user.id, 'system_user');
|
||||
cy.reload();
|
||||
verifyMemberDropdownAction(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const demoteToMember = (user, sysadmin) => {
|
||||
cy.externalRequest({user: sysadmin, method: 'put', path: `users/${user.id}/roles`, data: {roles: 'system_user'}});
|
||||
};
|
||||
|
||||
const promoteToSysAdmin = (user, sysadmin) => {
|
||||
cy.externalRequest({user: sysadmin, method: 'put', path: `users/${user.id}/roles`, data: {roles: 'system_user system_admin'}});
|
||||
};
|
||||
|
||||
function verifyMemberDropdownAction(hasActionItem) {
|
||||
// # Click member count to open member rhs
|
||||
cy.get('#member_rhs').click();
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
import {getAdminAccount} from '../../../support/env';
|
||||
|
||||
const demoteToMember = (user, admin) => {
|
||||
cy.externalRequest({user: admin, method: 'put', path: `users/${user.id}/roles`, data: {roles: 'system_user'}});
|
||||
};
|
||||
|
||||
const demoteToChannelMember = (user, channelId, admin) => {
|
||||
cy.externalRequest({
|
||||
user: admin,
|
||||
@@ -70,7 +66,7 @@ describe('Change Roles', () => {
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
|
||||
// # Make user a regular member for channel and system
|
||||
demoteToMember(testUser, admin);
|
||||
cy.externalUpdateUserRoles(user.id, 'system_user');
|
||||
demoteToChannelMember(testUser, testChannelId, admin);
|
||||
|
||||
// # Reload page to ensure no cache or saved information
|
||||
|
||||
@@ -53,13 +53,8 @@ function createChannel(channelType, teamId, userToAdd = null) {
|
||||
if (userToAdd) {
|
||||
// # Get user profile by email
|
||||
return cy.apiGetUserByEmail(userToAdd.email).then(({user}) => {
|
||||
// # Add user to team
|
||||
cy.externalRequest({
|
||||
user: admin,
|
||||
method: 'post',
|
||||
path: `channels/${channel.id}/members`,
|
||||
data: {user_id: user.id},
|
||||
}).then(() => {
|
||||
// # Add user to channel
|
||||
cy.externalAddUserToChannel(user.id, channel.id).then(() => {
|
||||
// # Explicitly wait to give some time to index before searching
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
return cy.wrap(channel);
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
// Group: @channels @enterprise @system_console
|
||||
|
||||
import * as TIMEOUTS from '../../../../../fixtures/timeouts';
|
||||
import {getAdminAccount} from '../../../../../support/env';
|
||||
|
||||
describe('System Console > Site Statistics', () => {
|
||||
let testTeam;
|
||||
@@ -102,8 +101,6 @@ describe('System Console > Site Statistics', () => {
|
||||
it('MM-T902 - Reporting ➜ Site statistics line graphs show same date', () => {
|
||||
cy.intercept('**/api/v4/**').as('resources');
|
||||
|
||||
const sysadmin = getAdminAccount();
|
||||
|
||||
let newChannel;
|
||||
|
||||
// # Create and visit new channel
|
||||
@@ -114,7 +111,7 @@ describe('System Console > Site Statistics', () => {
|
||||
// # Create a bot and get userID
|
||||
cy.apiCreateBot().then(({bot}) => {
|
||||
const botUserId = bot.user_id;
|
||||
cy.externalRequest({user: sysadmin, method: 'put', path: `users/${botUserId}/roles`, data: {roles: 'system_user system_post_all system_admin'}});
|
||||
cy.externalUpdateUserRoles(botUserId, 'system_user system_post_all system_admin');
|
||||
|
||||
// # Get token from bots id
|
||||
cy.apiAccessToken(botUserId, 'Create token').then(({token}) => {
|
||||
|
||||
@@ -60,7 +60,7 @@ const setUserTeamAndChannelMemberships = (user, team, channel, channelAdmin = fa
|
||||
const admin = getAdminAccount();
|
||||
|
||||
// # Set user as regular system user
|
||||
cy.externalRequest({user: admin, method: 'put', path: `users/${user.id}/roles`, data: {roles: 'system_user'}});
|
||||
cy.externalUpdateUserRoles(user.id, 'system_user');
|
||||
|
||||
// # Get team membership
|
||||
cy.externalRequest({user: admin, method: 'put', path: `teams/${team.id}/members/${user.id}/schemeRoles`, data: {scheme_user: true, scheme_admin: teamAdmin}});
|
||||
|
||||
@@ -91,7 +91,7 @@ const setUserTeamAndChannelMemberships = (user, team, channel, channelAdmin = fa
|
||||
const admin = getAdminAccount();
|
||||
|
||||
// # Set user as regular system user
|
||||
cy.externalRequest({user: admin, method: 'put', path: `users/${user.id}/roles`, data: {roles: 'system_user'}});
|
||||
cy.externalUpdateUserRoles(user.id, 'system_user');
|
||||
|
||||
// # Get team membership
|
||||
cy.externalRequest({user: admin, method: 'put', path: `teams/${team.id}/members/${user.id}/schemeRoles`, data: {scheme_user: true, scheme_admin: teamAdmin}});
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {getAdminAccount} from '../../../../tests/support/env';
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @messaging
|
||||
|
||||
describe('loading of at-mentioned users', () => {
|
||||
const admin = getAdminAccount();
|
||||
|
||||
let testChannel;
|
||||
|
||||
before(() => {
|
||||
// # Login as test user and visit off-topic
|
||||
cy.apiInitSetup({loginAfter: true}).then(({channel, channelUrl}) => {
|
||||
testChannel = channel;
|
||||
|
||||
cy.visit(channelUrl);
|
||||
|
||||
// # Wait for the channel to visibly load
|
||||
cy.findByText('Write to ' + testChannel.display_name);
|
||||
});
|
||||
});
|
||||
|
||||
it('should load a user who joins the channel', () => {
|
||||
cy.externalCreateUser({}).then((otherUser) => {
|
||||
// * The new user shouldn't be loaded because the current user hasn't seen them yet
|
||||
assertUserNotLoaded(otherUser.id);
|
||||
|
||||
// # Have the admin add them to the team and channel
|
||||
cy.externalAddUserToTeam(otherUser.id, testChannel.team_id);
|
||||
cy.externalAddUserToChannel(otherUser.id, testChannel.id);
|
||||
|
||||
// * Wait for the system message at-mentioning that user to know that they've been loaded
|
||||
cy.contains('a', '@' + otherUser.username).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('should load a user who posts in the channel', () => {
|
||||
cy.externalCreateUser({}).then((otherUser) => {
|
||||
// # Make the new user into an admin so that they can post without joining the channel to simulate
|
||||
// someone posting in the channel for the first time in a long time
|
||||
cy.externalUpdateUserRoles(otherUser.id, 'system_user system_admin');
|
||||
|
||||
// * The new user shouldn't be loaded because the current user hasn't seen them yet
|
||||
assertUserNotLoaded(otherUser.id);
|
||||
|
||||
// # Make a post as the other user
|
||||
cy.externalCreatePostAsUser(otherUser, {
|
||||
channel_id: testChannel.id,
|
||||
message: 'This is a post',
|
||||
}).then((post) => {
|
||||
cy.findByText(post.message).should('be.visible');
|
||||
cy.get(`#${post.id}_message`).should('be.visible');
|
||||
});
|
||||
|
||||
// * Wait for the user's name to know that they've been loaded
|
||||
cy.contains('button.user-popover', otherUser.username).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it("should load a user who's been at-mentioned in a post", () => {
|
||||
cy.externalCreateUser({}).then((otherUser) => {
|
||||
// * The new user shouldn't be loaded because the current user hasn't seen them yet
|
||||
assertUserNotLoaded(otherUser.id);
|
||||
|
||||
// # Have the admin at-mention the new user
|
||||
cy.externalCreatePostAsUser(admin, {
|
||||
channel_id: testChannel.id,
|
||||
message: `Created @${otherUser.username}`,
|
||||
});
|
||||
|
||||
// * Confirm that the at-mention renders as a link
|
||||
cy.contains('a', '@' + otherUser.username).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it("should load a user who's been at-mentioned in a message attachment's text", () => {
|
||||
cy.externalCreateUser({}).then((otherUser) => {
|
||||
// * The new user shouldn't be loaded because the current user hasn't seen them yet
|
||||
assertUserNotLoaded(otherUser.id);
|
||||
|
||||
// # Have the admin at-mention the new user
|
||||
cy.externalCreatePostAsUser(admin, {
|
||||
channel_id: testChannel.id,
|
||||
props: {
|
||||
attachments: [
|
||||
{text: `Ticket updated by @${otherUser.username}`},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// * Confirm that the at-mention renders as a link
|
||||
cy.contains('a', '@' + otherUser.username).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it("should load a user who's been at-mentioned in a message attachment's pretext", () => {
|
||||
cy.externalCreateUser({}).then((otherUser) => {
|
||||
// * The new user shouldn't be loaded because the current user hasn't seen them yet
|
||||
assertUserNotLoaded(otherUser.id);
|
||||
|
||||
// # Have the admin at-mention the new user
|
||||
cy.externalCreatePostAsUser(admin, {
|
||||
channel_id: testChannel.id,
|
||||
props: {
|
||||
attachments: [
|
||||
{pretext: `@${otherUser.username} created a ticket`, text: 'Ticket #123 - Fix some bug'},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// * Confirm that the at-mention renders as a link
|
||||
cy.contains('a', '@' + otherUser.username).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it("should not load a user who's been at-mentioned in a message attachment's title", () => {
|
||||
cy.externalCreateUser({}).then((otherUser) => {
|
||||
// * The new user shouldn't be loaded because the current user hasn't seen them yet
|
||||
assertUserNotLoaded(otherUser.id);
|
||||
|
||||
// # Have the admin at-mention the new user
|
||||
cy.externalCreatePostAsUser(admin, {
|
||||
channel_id: testChannel.id,
|
||||
props: {
|
||||
attachments: [
|
||||
{title: `@${otherUser.username}'s ticket`, text: 'TODO'},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// * Confirm that the attachment title doesn't render as a link
|
||||
cy.contains('h1', `@${otherUser.username}'s ticket`).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it("should not load a user who's been at-mentioned in a message attachment's field's title", () => {
|
||||
cy.externalCreateUser({}).then((otherUser) => {
|
||||
// * The new user shouldn't be loaded because the current user hasn't seen them yet
|
||||
assertUserNotLoaded(otherUser.id);
|
||||
|
||||
// # Have the admin at-mention the new user
|
||||
cy.externalCreatePostAsUser(admin, {
|
||||
channel_id: testChannel.id,
|
||||
props: {
|
||||
attachments: [
|
||||
{
|
||||
title: 'Ticket created',
|
||||
fields: [
|
||||
{title: `Note from @${otherUser.username}`, value: 'Something happened'},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// * Confirm that the field title doesn't render as a link
|
||||
cy.contains('th', `Note from @${otherUser.username}`).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it("should load a user who's been at-mentioned in a message attachment's field's value", () => {
|
||||
cy.externalCreateUser({}).then((otherUser) => {
|
||||
// * The new user shouldn't be loaded because the current user hasn't seen them yet
|
||||
assertUserNotLoaded(otherUser.id);
|
||||
|
||||
// # Have the admin at-mention the new user
|
||||
cy.externalCreatePostAsUser(admin, {
|
||||
channel_id: testChannel.id,
|
||||
props: {
|
||||
attachments: [
|
||||
{
|
||||
title: 'Ticket created',
|
||||
fields: [
|
||||
{title: 'Assignee', value: `Created @${otherUser.username}`},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// * Confirm that the at-mention renders as a link
|
||||
cy.contains('a', '@' + otherUser.username).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function assertUserNotLoaded(userId: string) {
|
||||
cy.window().then((win) => {
|
||||
const state = (win as any).store.getState();
|
||||
|
||||
cy.wrap(state.entities.users.profiles[userId]).should('be.undefined');
|
||||
});
|
||||
}
|
||||
@@ -10,10 +10,7 @@
|
||||
// Stage: @prod
|
||||
// Group: @channels @messaging
|
||||
|
||||
import {getAdminAccount} from '../../../support/env';
|
||||
|
||||
describe('Messaging', () => {
|
||||
const sysadmin = getAdminAccount();
|
||||
let newChannel;
|
||||
|
||||
before(() => {
|
||||
@@ -31,7 +28,7 @@ describe('Messaging', () => {
|
||||
// # Create a bot and get userID
|
||||
cy.apiCreateBot().then(({bot}) => {
|
||||
const botUserId = bot.user_id;
|
||||
cy.externalRequest({user: sysadmin, method: 'put', path: `users/${botUserId}/roles`, data: {roles: 'system_user system_post_all system_admin'}});
|
||||
cy.externalUpdateUserRoles(botUserId, 'system_user system_post_all system_admin');
|
||||
|
||||
// # Get token from bots id
|
||||
cy.apiAccessToken(botUserId, 'Create token').then(({token}) => {
|
||||
@@ -98,7 +95,7 @@ describe('Messaging', () => {
|
||||
// # Create a bot and get userID
|
||||
cy.apiCreateBot().then(({bot}) => {
|
||||
const botUserId = bot.user_id;
|
||||
cy.externalRequest({user: sysadmin, method: 'put', path: `users/${botUserId}/roles`, data: {roles: 'system_user system_post_all system_admin'}});
|
||||
cy.externalUpdateUserRoles(botUserId, 'system_user system_post_all system_admin');
|
||||
|
||||
// # Get token from bots id
|
||||
cy.apiAccessToken(botUserId, 'Create token').then(({token}) => {
|
||||
|
||||
@@ -10,14 +10,11 @@
|
||||
// Stage: @prod
|
||||
// Group: @channels @multi_team_and_dm
|
||||
|
||||
import {getAdminAccount} from '../../../support/env';
|
||||
|
||||
describe('Multi Team and DM', () => {
|
||||
let testChannel;
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let otherUser;
|
||||
const sysadmin = getAdminAccount();
|
||||
|
||||
before(() => {
|
||||
// # Setup with the new team, channel and user
|
||||
@@ -55,7 +52,7 @@ describe('Multi Team and DM', () => {
|
||||
cy.findByLabelText('off-topic public channel').click();
|
||||
|
||||
// # Add second user to team in external session
|
||||
cy.externalRequest({user: sysadmin, method: 'post', path: `teams/${testTeam.id}/members`, data: {team_id: testTeam.id, user_id: otherUser.id}});
|
||||
cy.externalAddUserToTeam(otherUser.id, testTeam.id);
|
||||
|
||||
// * Assert that Town Square is still marked as read after second user added to team
|
||||
cy.findByLabelText('town square public channel').should('be.visible');
|
||||
|
||||
@@ -98,8 +98,7 @@ export function setupTestData(data, {team, channel, admin, anotherAdmin}) {
|
||||
} = data;
|
||||
|
||||
// # Create another admin user so we can create override create_at of posts
|
||||
const baseUrl = Cypress.config('baseUrl');
|
||||
cy.externalRequest({user: admin, method: 'put', baseUrl, path: `users/${anotherAdmin.id}/roles`, data: {roles: 'system_user system_admin'}});
|
||||
cy.externalUpdateUserRoles(anotherAdmin.id, 'system_user system_admin');
|
||||
|
||||
// # Create a post from today
|
||||
cy.get('#postListContent', {timeout: TIMEOUTS.HALF_MIN}).should('be.visible');
|
||||
|
||||
@@ -11,10 +11,8 @@
|
||||
// Group: @channels @system_console @smoke
|
||||
|
||||
import * as TIMEOUTS from '../../../fixtures/timeouts';
|
||||
import {getAdminAccount} from '../../../support/env';
|
||||
|
||||
describe('System Console', () => {
|
||||
const sysadmin = getAdminAccount();
|
||||
let testUser;
|
||||
|
||||
before(() => {
|
||||
@@ -25,10 +23,8 @@ describe('System Console', () => {
|
||||
});
|
||||
|
||||
it('MM-T922 Demoted user cannot continue to view System Console', () => {
|
||||
const baseUrl = Cypress.config('baseUrl');
|
||||
|
||||
// # Set user to be a sysadmin, so it can access the system console
|
||||
cy.externalRequest({user: sysadmin, method: 'put', baseUrl, path: `users/${testUser.id}/roles`, data: {roles: 'system_user system_admin'}});
|
||||
cy.externalUpdateUserRoles(testUser.id, 'system_user system_admin');
|
||||
|
||||
// # Visit a page on the system console
|
||||
cy.visit('/admin_console/reporting/system_analytics');
|
||||
@@ -36,7 +32,7 @@ describe('System Console', () => {
|
||||
cy.url().should('include', '/admin_console/reporting/system_analytics');
|
||||
|
||||
// # Change the role of the user back to user
|
||||
cy.externalRequest({user: sysadmin, method: 'put', baseUrl, path: `users/${testUser.id}/roles`, data: {roles: 'system_user'}});
|
||||
cy.externalUpdateUserRoles(testUser.id, 'system_user');
|
||||
|
||||
// # User should get redirected to town square
|
||||
cy.get('#adminConsoleWrapper').should('not.exist');
|
||||
|
||||
@@ -102,17 +102,7 @@ describe('Handle new post', () => {
|
||||
const channel = response.data;
|
||||
|
||||
// # And then invite the current user
|
||||
cy.externalRequest({
|
||||
user: admin,
|
||||
baseUrl,
|
||||
method: 'post',
|
||||
path: `channels/${channel.id}/members`,
|
||||
data: {
|
||||
user_id: user1.id,
|
||||
},
|
||||
}).then((addResponse) => {
|
||||
expect(addResponse.status).to.equal(201);
|
||||
});
|
||||
cy.externalAddUserToChannel(user1.id, channel.id);
|
||||
});
|
||||
|
||||
// * Verify that the channel is in the current user's sidebar and is unread with one mention
|
||||
@@ -145,17 +135,7 @@ describe('Handle new post', () => {
|
||||
cy.delayRequestToRoutes([`channels/${channel.id}`], 100);
|
||||
|
||||
// # And then invite the current user
|
||||
cy.externalRequest({
|
||||
user: admin,
|
||||
baseUrl,
|
||||
method: 'post',
|
||||
path: `channels/${channel.id}/members`,
|
||||
data: {
|
||||
user_id: user1.id,
|
||||
},
|
||||
}).then((addResponse) => {
|
||||
expect(addResponse.status).to.equal(201);
|
||||
});
|
||||
cy.externalAddUserToChannel(user1.id, channel.id);
|
||||
});
|
||||
|
||||
// * Verify that the channel is in the current user's sidebar and is unread with one mention
|
||||
|
||||
30
e2e-tests/cypress/tests/support/external_commands.d.ts
поставляемый
30
e2e-tests/cypress/tests/support/external_commands.d.ts
поставляемый
@@ -1,30 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
/// <reference types="cypress" />
|
||||
|
||||
// ***************************************************************
|
||||
// Each command should be properly documented using JSDoc.
|
||||
// See https://jsdoc.app/index.html for reference.
|
||||
// Basic requirements for documentation are the following:
|
||||
// - Meaningful description
|
||||
// - Each parameter with `@params`
|
||||
// - Return value with `@returns`
|
||||
// - Example usage with `@example`
|
||||
// Custom command should follow naming convention of having `external` prefix, e.g. `externalActivateUser`.
|
||||
// ***************************************************************
|
||||
|
||||
declare namespace Cypress {
|
||||
interface Chainable {
|
||||
|
||||
/**
|
||||
* Makes an external request as a sysadmin and activate/deactivate a user directly via API
|
||||
* @param {String} userId - The user ID
|
||||
* @param {Boolean} active - Whether to activate or deactivate - true/false
|
||||
*
|
||||
* @example
|
||||
* cy.externalActivateUser('user-id', false);
|
||||
*/
|
||||
externalActivateUser(userId: string, activate: boolean): Chainable;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {getAdminAccount} from './env';
|
||||
|
||||
Cypress.Commands.add('externalActivateUser', (userId, active = true) => {
|
||||
const baseUrl = Cypress.config('baseUrl');
|
||||
const admin = getAdminAccount();
|
||||
|
||||
cy.externalRequest({user: admin, method: 'put', baseUrl, path: `users/${userId}/active`, data: {active}});
|
||||
});
|
||||
138
e2e-tests/cypress/tests/support/external_commands.ts
Обычный файл
138
e2e-tests/cypress/tests/support/external_commands.ts
Обычный файл
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {ChannelMembership} from '@mattermost/types/channels';
|
||||
import type {Post} from '@mattermost/types/posts';
|
||||
import type {TeamMembership} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {getAdminAccount} from './env';
|
||||
|
||||
import {getRandomId} from '../utils';
|
||||
|
||||
function externalActivateUser(userId: string, active = true) {
|
||||
const admin = getAdminAccount();
|
||||
|
||||
cy.externalRequest({user: admin, method: 'PUT', path: `users/${userId}/active`, data: {active}});
|
||||
}
|
||||
Cypress.Commands.add('externalActivateUser', externalActivateUser);
|
||||
|
||||
function externalAddUserToChannel(userId: string, channelId: string): Cypress.Chainable<ChannelMembership> {
|
||||
const admin = getAdminAccount();
|
||||
|
||||
return cy.externalRequest({
|
||||
user: admin,
|
||||
method: 'POST',
|
||||
path: `channels/${channelId}/members`,
|
||||
data: {
|
||||
user_id: userId,
|
||||
},
|
||||
}).then((response) => response.data);
|
||||
}
|
||||
Cypress.Commands.add('externalAddUserToChannel', externalAddUserToChannel);
|
||||
|
||||
function externalAddUserToTeam(userId: string, teamId: string): Cypress.Chainable<TeamMembership> {
|
||||
const admin = getAdminAccount();
|
||||
|
||||
return cy.externalRequest({
|
||||
user: admin,
|
||||
method: 'POST',
|
||||
path: `teams/${teamId}/members`,
|
||||
data: {
|
||||
team_id: teamId,
|
||||
user_id: userId,
|
||||
},
|
||||
}).then((response) => response.data);
|
||||
}
|
||||
Cypress.Commands.add('externalAddUserToTeam', externalAddUserToTeam);
|
||||
|
||||
function externalCreatePostAsUser(user: Pick<UserProfile, 'username' | 'password'>, post: Partial<Post>): Cypress.Chainable<Post> {
|
||||
return cy.externalRequest({
|
||||
user,
|
||||
method: 'POST',
|
||||
path: 'posts',
|
||||
data: post,
|
||||
}).then((response) => response.data);
|
||||
}
|
||||
Cypress.Commands.add('externalCreatePostAsUser', externalCreatePostAsUser);
|
||||
|
||||
function externalCreateUser(user: Partial<UserProfile>): Cypress.Chainable<UserProfile> {
|
||||
const admin = getAdminAccount();
|
||||
|
||||
const randomValue = getRandomId();
|
||||
|
||||
return cy.externalRequest({
|
||||
user: admin,
|
||||
method: 'POST',
|
||||
path: 'users',
|
||||
data: {
|
||||
username: 'user' + randomValue,
|
||||
email: 'email' + randomValue + '@example.mattermost.com',
|
||||
password: 'password' + randomValue,
|
||||
...user,
|
||||
},
|
||||
}).then((response) => {
|
||||
// Re-add the password to the result so that we can make requests as that user
|
||||
return {
|
||||
...response.data,
|
||||
password: 'password' + randomValue,
|
||||
};
|
||||
});
|
||||
}
|
||||
Cypress.Commands.add('externalCreateUser', externalCreateUser);
|
||||
|
||||
function externalUpdateUserRoles(userId: string, roles: string): Cypress.Chainable<unknown> {
|
||||
const admin = getAdminAccount();
|
||||
|
||||
return cy.externalRequest({
|
||||
user: admin,
|
||||
method: 'PUT',
|
||||
path: `users/${userId}/roles`,
|
||||
data: {roles},
|
||||
});
|
||||
}
|
||||
Cypress.Commands.add('externalUpdateUserRoles', externalUpdateUserRoles);
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
|
||||
/**
|
||||
* Makes an external request as a sysadmin and activate/deactivate a user directly via API
|
||||
* @param {String} userId - The user ID
|
||||
* @param {Boolean} active - Whether to activate or deactivate - true/false
|
||||
*
|
||||
* @example
|
||||
* cy.externalActivateUser('user-id', false);
|
||||
*/
|
||||
externalActivateUser: typeof externalActivateUser;
|
||||
|
||||
/**
|
||||
* As the system admin, adds a user to a channel.
|
||||
*/
|
||||
externalAddUserToChannel: typeof externalAddUserToChannel;
|
||||
|
||||
/**
|
||||
* As the system admin, adds a user to a team.
|
||||
*/
|
||||
externalAddUserToTeam: typeof externalAddUserToTeam;
|
||||
|
||||
/**
|
||||
* As the given user, creates a post via the API.
|
||||
*/
|
||||
externalCreatePostAsUser: typeof externalCreatePostAsUser;
|
||||
|
||||
/**
|
||||
* As the system admin, creates a new user via the API. The user is automatically given a username, email,
|
||||
* and password, but the override parameter can be be used to specify any other fields if needed.
|
||||
*/
|
||||
externalCreateUser: typeof externalCreateUser;
|
||||
|
||||
/**
|
||||
* As the system admin, updates a user's roles via the API.
|
||||
*/
|
||||
externalUpdateUserRoles: typeof externalUpdateUserRoles;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1410,6 +1410,12 @@ func getMentionsEnabledFields(post *model.Post) model.StringArray {
|
||||
if attachment.Text != "" {
|
||||
ret = append(ret, attachment.Text)
|
||||
}
|
||||
|
||||
for _, field := range attachment.Fields {
|
||||
if valueString, ok := field.Value.(string); ok && valueString != "" {
|
||||
ret = append(ret, valueString)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -1337,6 +1337,25 @@ func TestGetExplicitMentions(t *testing.T) {
|
||||
HereMentioned: true,
|
||||
},
|
||||
},
|
||||
"should include the mentions from attachment field values (but not field titles)": {
|
||||
Message: "this is a message",
|
||||
Attachments: []*model.SlackAttachment{
|
||||
{
|
||||
Fields: []*model.SlackAttachmentField{
|
||||
{
|
||||
Title: "@user1",
|
||||
Value: "@user2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Keywords: map[string][]string{"@user1": {id1}, "@user2": {id2}},
|
||||
Expected: &MentionResults{
|
||||
Mentions: map[string]MentionType{
|
||||
id2: KeywordMention,
|
||||
},
|
||||
},
|
||||
},
|
||||
"Name on keywords is a prefix of a mention": {
|
||||
Message: "@other @test-two",
|
||||
Keywords: map[string][]string{"@test": {model.NewId()}},
|
||||
@@ -2167,6 +2186,12 @@ func TestGetMentionsEnabledFields(t *testing.T) {
|
||||
|
||||
attachmentWithOutPreText := model.SlackAttachment{
|
||||
Text: "some text",
|
||||
Fields: []*model.SlackAttachmentField{
|
||||
{
|
||||
Title: "field title",
|
||||
Value: "field value",
|
||||
},
|
||||
},
|
||||
}
|
||||
attachments := []*model.SlackAttachment{
|
||||
&attachmentWithTextAndPreText,
|
||||
@@ -2183,11 +2208,12 @@ func TestGetMentionsEnabledFields(t *testing.T) {
|
||||
"This is the message",
|
||||
"@Channel some comment for the channel",
|
||||
"@here with mentions",
|
||||
"some text"}
|
||||
"some text",
|
||||
"field value",
|
||||
}
|
||||
|
||||
mentionEnabledFields := getMentionsEnabledFields(post)
|
||||
|
||||
assert.EqualValues(t, 4, len(mentionEnabledFields))
|
||||
assert.EqualValues(t, expectedFields, mentionEnabledFields)
|
||||
}
|
||||
|
||||
|
||||
@@ -424,6 +424,7 @@ exports[`components/post_view/MessageAttachment should match snapshot when the a
|
||||
message="Do you like https://mattermost.com?"
|
||||
options={
|
||||
Object {
|
||||
"atMentions": false,
|
||||
"autolinkedUrlSchemes": Array [],
|
||||
"mentionHighlight": false,
|
||||
"renderer": LinkOnlyRenderer {
|
||||
@@ -469,6 +470,7 @@ exports[`components/post_view/MessageAttachment should match snapshot when the a
|
||||
message="Do you like :pizza:?"
|
||||
options={
|
||||
Object {
|
||||
"atMentions": false,
|
||||
"autolinkedUrlSchemes": Array [],
|
||||
"mentionHighlight": false,
|
||||
"renderer": LinkOnlyRenderer {
|
||||
@@ -514,6 +516,7 @@ exports[`components/post_view/MessageAttachment should match snapshot when the a
|
||||
message="Don't you like emojis?"
|
||||
options={
|
||||
Object {
|
||||
"atMentions": false,
|
||||
"autolinkedUrlSchemes": Array [],
|
||||
"mentionHighlight": false,
|
||||
"renderer": LinkOnlyRenderer {
|
||||
@@ -559,6 +562,7 @@ exports[`components/post_view/MessageAttachment should match snapshot when the f
|
||||
message="footer"
|
||||
options={
|
||||
Object {
|
||||
"atMentions": false,
|
||||
"autolinkedUrlSchemes": Array [],
|
||||
"mentionHighlight": false,
|
||||
"renderer": LinkOnlyRenderer {
|
||||
@@ -620,6 +624,7 @@ exports[`components/post_view/MessageAttachment should match value on getFieldsT
|
||||
message="title_1"
|
||||
options={
|
||||
Object {
|
||||
"atMentions": false,
|
||||
"markdown": false,
|
||||
"mentionHighlight": false,
|
||||
}
|
||||
@@ -654,6 +659,7 @@ exports[`components/post_view/MessageAttachment should match value on getFieldsT
|
||||
message="title_2"
|
||||
options={
|
||||
Object {
|
||||
"atMentions": false,
|
||||
"markdown": false,
|
||||
"mentionHighlight": false,
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ export default class MessageAttachment extends React.PureComponent<Props, State>
|
||||
let rowPos = 0;
|
||||
let lastWasLong = false;
|
||||
let nrTables = 0;
|
||||
const markdown = {markdown: false, mentionHighlight: false};
|
||||
const markdown = {markdown: false, mentionHighlight: false, atMentions: false};
|
||||
|
||||
fields.forEach((field: MessageAttachmentField, i: number) => {
|
||||
if (rowPos === 2 || !(field.short === true) || lastWasLong) {
|
||||
@@ -421,6 +421,7 @@ export default class MessageAttachment extends React.PureComponent<Props, State>
|
||||
<Markdown
|
||||
message={attachment.title}
|
||||
options={{
|
||||
atMentions: false,
|
||||
mentionHighlight: false,
|
||||
renderer: new LinkOnlyRenderer(),
|
||||
autolinkedUrlSchemes: [],
|
||||
|
||||
@@ -570,7 +570,7 @@ describe('Actions.Posts', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('getNeededAtMentionedUsernames', async () => {
|
||||
describe('getNeededAtMentionedUsernames', () => {
|
||||
const state = {
|
||||
entities: {
|
||||
users: {
|
||||
@@ -592,65 +592,55 @@ describe('Actions.Posts', () => {
|
||||
},
|
||||
} as unknown as GlobalState;
|
||||
|
||||
expect(
|
||||
Actions.getNeededAtMentionedUsernamesAndGroups(state, [
|
||||
TestHelper.getPostMock({message: 'aaa'}),
|
||||
])).toEqual(
|
||||
new Set(),
|
||||
);
|
||||
|
||||
expect(
|
||||
Actions.getNeededAtMentionedUsernamesAndGroups(state, [
|
||||
TestHelper.getPostMock({message: '@aaa'}),
|
||||
])).toEqual(
|
||||
new Set(),
|
||||
);
|
||||
|
||||
expect(
|
||||
Actions.getNeededAtMentionedUsernamesAndGroups(state, [
|
||||
TestHelper.getPostMock({message: '@zzz'}),
|
||||
])).toEqual(
|
||||
new Set(),
|
||||
);
|
||||
|
||||
expect(
|
||||
Actions.getNeededAtMentionedUsernamesAndGroups(state, [
|
||||
TestHelper.getPostMock({message: '@aaa @bbb @ccc @zzz'}),
|
||||
])).toEqual(
|
||||
new Set(['bbb', 'ccc']),
|
||||
);
|
||||
|
||||
expect(
|
||||
Actions.getNeededAtMentionedUsernamesAndGroups(state, [
|
||||
TestHelper.getPostMock({message: '@bbb. @ccc.ddd'}),
|
||||
])).toEqual(
|
||||
new Set(['bbb.', 'bbb', 'ccc.ddd']),
|
||||
);
|
||||
|
||||
expect(
|
||||
Actions.getNeededAtMentionedUsernamesAndGroups(state, [
|
||||
TestHelper.getPostMock({message: '@bbb- @ccc-ddd'}),
|
||||
])).toEqual(
|
||||
new Set(['bbb-', 'bbb', 'ccc-ddd']),
|
||||
);
|
||||
|
||||
expect(
|
||||
Actions.getNeededAtMentionedUsernamesAndGroups(state, [
|
||||
TestHelper.getPostMock({message: '@bbb_ @ccc_ddd'}),
|
||||
])).toEqual(
|
||||
new Set(['bbb_', 'ccc_ddd']),
|
||||
);
|
||||
|
||||
expect(
|
||||
Actions.getNeededAtMentionedUsernamesAndGroups(state, [
|
||||
TestHelper.getPostMock({message: '(@bbb/@ccc) ddd@eee'}),
|
||||
])).toEqual(
|
||||
new Set(['bbb', 'ccc']),
|
||||
);
|
||||
|
||||
expect(
|
||||
Actions.getNeededAtMentionedUsernamesAndGroups(state, [
|
||||
TestHelper.getPostMock({
|
||||
const testCases = [
|
||||
{
|
||||
name: "shouldn't return anything when no users are at-mentioned",
|
||||
input: TestHelper.getPostMock({message: 'aaa'}),
|
||||
expected: new Set(),
|
||||
},
|
||||
{
|
||||
name: "shouldn't return anything for a user that's already loaded",
|
||||
input: TestHelper.getPostMock({message: '@aaa'}),
|
||||
expected: new Set(),
|
||||
},
|
||||
{
|
||||
name: "shouldn't return anything for a group that's already loaded",
|
||||
input: TestHelper.getPostMock({message: '@zzz'}),
|
||||
expected: new Set(),
|
||||
},
|
||||
{
|
||||
name: 'should return any unrecognized at-mentions',
|
||||
input: TestHelper.getPostMock({message: '@aaa @bbb @ccc @zzz'}),
|
||||
expected: new Set(['bbb', 'ccc']),
|
||||
},
|
||||
{
|
||||
name: 'should return at-mentions followed by period both with and without the period',
|
||||
input: TestHelper.getPostMock({message: '@bbb. @ccc.ddd'}),
|
||||
expected: new Set(['bbb.', 'bbb', 'ccc.ddd']),
|
||||
},
|
||||
{
|
||||
name: 'should return at-mentions followed by hyphen both with and without the hyphen',
|
||||
input: TestHelper.getPostMock({message: '@bbb- @ccc-ddd'}),
|
||||
expected: new Set(['bbb-', 'bbb', 'ccc-ddd']),
|
||||
},
|
||||
{
|
||||
name: 'should return at-mentions followed by underscores with the underscore',
|
||||
input: TestHelper.getPostMock({message: '@bbb_ @ccc_ddd'}),
|
||||
expected: new Set(['bbb_', 'ccc_ddd']),
|
||||
},
|
||||
{
|
||||
name: 'should return at-mentions in brackets',
|
||||
input: TestHelper.getPostMock({message: '(@bbb/@ccc)'}),
|
||||
expected: new Set(['bbb', 'ccc']),
|
||||
},
|
||||
{
|
||||
name: "shouldn't return anything when an at sign is in the middle of a word",
|
||||
input: TestHelper.getPostMock({message: 'ddd@eee'}),
|
||||
expected: new Set(),
|
||||
},
|
||||
{
|
||||
name: 'should return at-mentions from inside message attachment props text and pretext',
|
||||
input: TestHelper.getPostMock({
|
||||
message: '@aaa @bbb',
|
||||
props: {
|
||||
attachments: [
|
||||
@@ -659,23 +649,55 @@ describe('Actions.Posts', () => {
|
||||
],
|
||||
},
|
||||
}),
|
||||
]),
|
||||
).toEqual(
|
||||
new Set(['bbb', 'ccc', 'ddd', 'eee', 'fff', 'ggg']),
|
||||
);
|
||||
expected: new Set(['bbb', 'ccc', 'ddd', 'eee', 'fff', 'ggg']),
|
||||
},
|
||||
{
|
||||
name: 'should return at-mentions from inside message attachment field values but not their titles',
|
||||
input: TestHelper.getPostMock({
|
||||
props: {
|
||||
attachments: [
|
||||
{
|
||||
fields: [
|
||||
{title: '@bbb', value: '@ccc'},
|
||||
{value: '@ddd'},
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{title: '@eee', value: '@fff'},
|
||||
{value: '@ggg'},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
expected: new Set(['ccc', 'ddd', 'fff', 'ggg']),
|
||||
},
|
||||
];
|
||||
|
||||
// should never try to request usernames matching special mentions
|
||||
expect(
|
||||
Actions.getNeededAtMentionedUsernamesAndGroups(state, [
|
||||
TestHelper.getPostMock({message: '@all'}),
|
||||
TestHelper.getPostMock({message: '@here'}),
|
||||
TestHelper.getPostMock({message: '@channel'}),
|
||||
TestHelper.getPostMock({message: '@all.'}),
|
||||
TestHelper.getPostMock({message: '@here.'}),
|
||||
TestHelper.getPostMock({message: '@channel.'}),
|
||||
])).toEqual(
|
||||
new Set(),
|
||||
);
|
||||
for (const specialMention of [
|
||||
'@all',
|
||||
'@here',
|
||||
'@channel',
|
||||
'@all.',
|
||||
'@here.',
|
||||
'@channel.',
|
||||
]) {
|
||||
testCases.push({
|
||||
name: `should never return special mentions (${specialMention})`,
|
||||
input: TestHelper.getPostMock({message: specialMention}),
|
||||
expected: new Set(),
|
||||
});
|
||||
}
|
||||
|
||||
for (const testCase of testCases) {
|
||||
test(testCase.name, () => {
|
||||
expect(Actions.getNeededAtMentionedUsernamesAndGroups(
|
||||
state,
|
||||
[testCase.input],
|
||||
)).toEqual(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('getPostsSince', async () => {
|
||||
|
||||
@@ -1090,6 +1090,12 @@ export function getNeededAtMentionedUsernamesAndGroups(state: GlobalState, posts
|
||||
for (const attachment of post.props.attachments) {
|
||||
findNeededUsernamesAndGroups(attachment.pretext);
|
||||
findNeededUsernamesAndGroups(attachment.text);
|
||||
|
||||
if (attachment.fields) {
|
||||
for (const field of attachment.fields) {
|
||||
findNeededUsernamesAndGroups(field.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user