forked from Ivasoft/mattermost-mobile
MM-41854 Detox/E2E: Setup detox infrastructure in Gekidou (#5979)
* MM-41854 Detox/E2E: Setup detox infrastructure in Gekidou * Fix lint issues * Fix lint issues * Update API to include baseUrl for multiple servers * Update init.js to have default siteUrl as baseUrl * Update init.js to have default siteUrl as baseUrl * Update import of testConfig * Update import of testConfig * Update postMessageAs signature * Update detox/webhook_server.js Co-authored-by: Avinash Lingaloo <avinashlng1080@gmail.com> Co-authored-by: Mattermod <mattermod@users.noreply.github.com> Co-authored-by: Avinash Lingaloo <avinashlng1080@gmail.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -97,6 +97,7 @@ coverage
|
||||
mattermost-license.txt
|
||||
*.mattermost-license
|
||||
detox/artifacts
|
||||
detox/detox_pixel_4_xl_api_30
|
||||
|
||||
# Bundle artifact
|
||||
*.jsbundle
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"binaryPath": "../ios/Build/Products/Debug-iphonesimulator/Mattermost.app",
|
||||
"type": "ios.simulator",
|
||||
"device": {
|
||||
"type": "iPhone 11"
|
||||
"type": "iPhone 13"
|
||||
}
|
||||
},
|
||||
"ios.sim.release": {
|
||||
@@ -14,23 +14,23 @@
|
||||
"binaryPath": "../ios/Build/Products/Release-iphonesimulator/Mattermost.app",
|
||||
"build": "cd ../fastlane && NODE_ENV=production bundle exec fastlane ios simulator && cd ../detox",
|
||||
"device": {
|
||||
"type": "iPhone 11"
|
||||
"type": "iPhone 13"
|
||||
}
|
||||
},
|
||||
"android.emu.debug": {
|
||||
"type": "android.emulator",
|
||||
"binaryPath": "../android/app/build/outputs/apk/debug/app-debug.apk",
|
||||
"build": "cd .. && ./node_modules/.bin/jetify && cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ../detox",
|
||||
"build": "cd .. && ./node_modules/.bin/jetify && cd android && ./gradlew clean && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ../detox",
|
||||
"device": {
|
||||
"avdName": "detox_emu_api_30"
|
||||
"avdName": "detox_pixel_4_xl_api_30"
|
||||
}
|
||||
},
|
||||
"android.emu.release": {
|
||||
"type": "android.emulator",
|
||||
"binaryPath": "../android/app/build/outputs/apk/release/app-release.apk",
|
||||
"build": "cd .. && ./node_modules/.bin/jetify && cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ../detox",
|
||||
"build": "cd .. && ./node_modules/.bin/jetify && cd android && ./gradlew clean && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ../detox",
|
||||
"device": {
|
||||
"avdName": "detox_emu_api_30"
|
||||
"avdName": "detox_pixel_4_xl_api_30"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
49
detox/android_emulator/config.ini
Normal file
49
detox/android_emulator/config.ini
Normal file
@@ -0,0 +1,49 @@
|
||||
AvdId = Detox_Pixel_4_XL_API_30
|
||||
PlayStore.enabled = false
|
||||
abi.type = x86
|
||||
avd.ini.displayname = Detox Pixel 4 XL API 30
|
||||
avd.ini.encoding = UTF-8
|
||||
disk.dataPartition.size = 6g
|
||||
fastboot.chosenSnapshotFile =
|
||||
fastboot.forceChosenSnapshotBoot = no
|
||||
fastboot.forceColdBoot = no
|
||||
fastboot.forceFastBoot = yes
|
||||
hw.accelerometer = no
|
||||
hw.arc = false
|
||||
hw.gyroscope = no
|
||||
hw.audioInput = no
|
||||
hw.audioOutput = no
|
||||
hw.battery = yes
|
||||
hw.camera.back = virtualscene
|
||||
hw.camera.front = emulated
|
||||
hw.cpu.arch = x86
|
||||
hw.cpu.ncore = 4
|
||||
hw.dPad = no
|
||||
hw.device.hash2 = MD5:80326cf5b53c08af25d4243cb231faa9
|
||||
hw.device.manufacturer = Google
|
||||
hw.device.name = pixel_4_xl
|
||||
hw.gps = no
|
||||
hw.gpu.enabled = yes
|
||||
hw.gpu.mode = auto
|
||||
hw.initialOrientation = Portrait
|
||||
hw.keyboard = yes
|
||||
hw.lcd.density = 560
|
||||
hw.lcd.height = 3040
|
||||
hw.lcd.width = 1440
|
||||
hw.mainKeys = no
|
||||
hw.ramSize = 2048
|
||||
hw.sdCard = no
|
||||
hw.sensors.orientation = yes
|
||||
hw.sensors.proximity = yes
|
||||
hw.trackBall = no
|
||||
image.sysdir.1 = system-images/android-30/google_apis/x86/
|
||||
runtime.network.latency = none
|
||||
runtime.network.speed = full
|
||||
sdcard.size = 0
|
||||
showDeviceFrame = yes
|
||||
skin.dynamic = yes
|
||||
skin.name = pixel_4_xl
|
||||
skin.path = /change_to_absolute_path/pixel_4_xl_skin
|
||||
tag.display = Google APIs
|
||||
tag.id = google_apis
|
||||
vm.heapSize = 576
|
||||
BIN
detox/android_emulator/pixel_4_xl_skin/back.webp
Normal file
BIN
detox/android_emulator/pixel_4_xl_skin/back.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 532 KiB |
36
detox/android_emulator/pixel_4_xl_skin/layout
Normal file
36
detox/android_emulator/pixel_4_xl_skin/layout
Normal file
@@ -0,0 +1,36 @@
|
||||
parts {
|
||||
device {
|
||||
display {
|
||||
width 1440
|
||||
height 3040
|
||||
x 0
|
||||
y 0
|
||||
}
|
||||
}
|
||||
portrait {
|
||||
background {
|
||||
image back.webp
|
||||
}
|
||||
foreground {
|
||||
mask mask.webp
|
||||
cutout emu01
|
||||
}
|
||||
}
|
||||
}
|
||||
layouts {
|
||||
portrait {
|
||||
width 1571
|
||||
height 3332
|
||||
event EV_SW:0:1
|
||||
part1 {
|
||||
name portrait
|
||||
x 0
|
||||
y 0
|
||||
}
|
||||
part2 {
|
||||
name device
|
||||
x 61
|
||||
y 195
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
detox/android_emulator/pixel_4_xl_skin/mask.webp
Normal file
BIN
detox/android_emulator/pixel_4_xl_skin/mask.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
1
detox/android_emulator/quickbootChoice.ini
Normal file
1
detox/android_emulator/quickbootChoice.ini
Normal file
@@ -0,0 +1 @@
|
||||
saveOnExit = true
|
||||
19
detox/create_android_emulator.sh
Executable file
19
detox/create_android_emulator.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
set -o pipefail
|
||||
|
||||
NAME=detox_pixel_4_xl_api_30
|
||||
|
||||
if emulator -list-avds | grep -q $NAME; then
|
||||
echo "'${NAME}' Android virtual device already exists."
|
||||
else
|
||||
# Create virtual device in a relative "detox_pixel_4_xl_api_30" folder
|
||||
avdmanager create avd -n $NAME -k 'system-images;android-30;google_apis;x86' -g google_apis -p $NAME -d 'pixel'
|
||||
|
||||
# Copy predefined config and skin
|
||||
cp -r android_emulator/ $NAME/
|
||||
sed -i -e "s|skin.path = /change_to_absolute_path/pixel_4_xl_skin|skin.path = $(pwd)/${NAME}/pixel_4_xl_skin|g" $NAME/config.ini
|
||||
|
||||
echo "Android virtual device successfully created: ${NAME}"
|
||||
fi
|
||||
@@ -1,13 +1,16 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {System, User} from '@support/server_api';
|
||||
import {Plugin, System, User} from '@support/server_api';
|
||||
import testConfig from '@support/test_config';
|
||||
|
||||
beforeAll(async () => {
|
||||
// Login as sysadmin and reset server configuration
|
||||
await System.apiCheckSystemHealth();
|
||||
await User.apiAdminLogin();
|
||||
await System.apiUpdateConfig();
|
||||
const baseUrl = testConfig.siteUrl;
|
||||
await System.apiCheckSystemHealth(baseUrl);
|
||||
await User.apiAdminLogin(baseUrl);
|
||||
await System.apiUpdateConfig(baseUrl);
|
||||
await Plugin.apiDisableNonPrepackagedPlugins(baseUrl);
|
||||
|
||||
await device.launchApp({
|
||||
newInstance: false,
|
||||
|
||||
46
detox/e2e/plugins/post_message_as.js
Normal file
46
detox/e2e/plugins/post_message_as.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
module.exports = async (baseUrl, {sender, message, channelId, rootId, createAt = 0}) => {
|
||||
const loginResponse = await axios({
|
||||
url: `${baseUrl}/api/v4/users/login`,
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
method: 'post',
|
||||
data: {login_id: sender.username, password: sender.password},
|
||||
});
|
||||
|
||||
const setCookie = loginResponse.headers['set-cookie'];
|
||||
let cookieString = '';
|
||||
setCookie.forEach((cookie) => {
|
||||
const nameAndValue = cookie.split(';')[0];
|
||||
cookieString += nameAndValue + ';';
|
||||
});
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await axios({
|
||||
url: `${baseUrl}/api/v4/posts`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
Cookie: cookieString,
|
||||
},
|
||||
method: 'post',
|
||||
data: {
|
||||
channel_id: channelId,
|
||||
message,
|
||||
type: '',
|
||||
create_at: createAt,
|
||||
root_id: rootId,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.response) {
|
||||
response = err.response;
|
||||
}
|
||||
}
|
||||
|
||||
return {status: response.status, data: response.data};
|
||||
};
|
||||
59
detox/e2e/support/server_api/bot.js
Normal file
59
detox/e2e/support/server_api/bot.js
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {capitalize, getRandomId} from '@support/utils';
|
||||
|
||||
import client from './client';
|
||||
import {getResponseFromError} from './common';
|
||||
|
||||
// ****************************************************************
|
||||
// Bots
|
||||
// See https://api.mattermost.com/#tag/bots
|
||||
//
|
||||
// Exported API function should have the following:
|
||||
// - documented using JSDoc
|
||||
// - meaningful description
|
||||
// - match the referenced API endpoints
|
||||
// - parameter/s defined by `@param`
|
||||
// - return value defined by `@return`
|
||||
// ****************************************************************
|
||||
|
||||
/**
|
||||
* Create a bot.
|
||||
* See https://api.mattermost.com/#operation/CreateBot
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} option.prefix - prefix to username and display name
|
||||
* @param {Object} option.bot - bot object to be created
|
||||
* @return {Object} returns {bot} on success or {error, status} on error
|
||||
*/
|
||||
export const apiCreateBot = async (baseUrl, {prefix = 'bot', bot = null} = {}) => {
|
||||
try {
|
||||
const newBot = bot || generateRandomBot({prefix});
|
||||
|
||||
const response = await client.post(
|
||||
`${baseUrl}/api/v4/bots`,
|
||||
newBot,
|
||||
);
|
||||
|
||||
return {bot: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const generateRandomBot = ({prefix = 'bot', randomIdLength = 6} = {}) => {
|
||||
const randomId = getRandomId(randomIdLength);
|
||||
|
||||
return {
|
||||
username: `${prefix}-${randomId}`,
|
||||
display_name: `${capitalize(prefix)} ${randomId}`,
|
||||
description: `Test bot description ${randomId}`,
|
||||
};
|
||||
};
|
||||
|
||||
export const Bot = {
|
||||
apiCreateBot,
|
||||
generateRandomBot,
|
||||
};
|
||||
|
||||
export default Bot;
|
||||
@@ -19,18 +19,40 @@ import {getResponseFromError} from './common';
|
||||
// ****************************************************************
|
||||
|
||||
/**
|
||||
* Create a channel.
|
||||
* See https://api.mattermost.com/#tag/channels/paths/~1channels/post
|
||||
* @param {string} option.teamId - The team ID of the team to create the channel on
|
||||
* @param {string} option.type - 'O' (default) for a public channel, 'P' for a private channel
|
||||
* @param {string} option.prefix - option to add prefix to name and display name
|
||||
* @param {Object} option.channel - fix channel object to be created
|
||||
* @return {Object} returns {channel} on success or {error, status} on error
|
||||
* Add user to channel.
|
||||
* See https://api.mattermost.com/#operation/AddChannelMember
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - The ID of user to add into the channel
|
||||
* @param {string} channelId - The channel ID
|
||||
* @return {Object} returns {member} on success or {error, status} on error
|
||||
*/
|
||||
export const apiCreateChannel = async ({teamId = null, type = 'O', prefix = 'channel', channel = null} = {}) => {
|
||||
export const apiAddUserToChannel = async (baseUrl, userId, channelId) => {
|
||||
try {
|
||||
const response = await client.post(
|
||||
'/api/v4/channels',
|
||||
`${baseUrl}/api/v4/channels/${channelId}/members`,
|
||||
{user_id: userId},
|
||||
);
|
||||
|
||||
return {member: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a channel.
|
||||
* See https://api.mattermost.com/#operation/CreateChannel
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} option.teamId - The team ID of the team to create the channel on
|
||||
* @param {string} option.type - 'O' (default) for a public channel, 'P' for a private channel
|
||||
* @param {string} option.prefix - prefix to name, display name, purpose, and header
|
||||
* @param {Object} option.channel - channel object to be created
|
||||
* @return {Object} returns {channel} on success or {error, status} on error
|
||||
*/
|
||||
export const apiCreateChannel = async (baseUrl, {teamId = null, type = 'O', prefix = 'channel', channel = null} = {}) => {
|
||||
try {
|
||||
const response = await client.post(
|
||||
`${baseUrl}/api/v4/channels`,
|
||||
channel || generateRandomChannel(teamId, type, prefix),
|
||||
);
|
||||
|
||||
@@ -41,15 +63,18 @@ export const apiCreateChannel = async ({teamId = null, type = 'O', prefix = 'cha
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a channel by name and team name.
|
||||
* See https://api.mattermost.com/#tag/channels/paths/~1teams~1name~1{team_name}~1channels~1name~1{channel_name}/get
|
||||
* @param {string} teamName - team name
|
||||
* @param {string} channelName - channel name
|
||||
* Create a direct message channel.
|
||||
* See https://api.mattermost.com/#operation/CreateDirectChannel
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {Array} userIds - the two user IDs to be in the direct message
|
||||
* @return {Object} returns {channel} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetChannelByName = async (teamName, channelName) => {
|
||||
export const apiCreateDirectChannel = async (baseUrl, userIds = []) => {
|
||||
try {
|
||||
const response = await client.get(`/api/v4/teams/name/${teamName}/channels/name/${channelName}`);
|
||||
const response = await client.post(
|
||||
`${baseUrl}/api/v4/channels/direct`,
|
||||
userIds,
|
||||
);
|
||||
|
||||
return {channel: response.data};
|
||||
} catch (err) {
|
||||
@@ -58,20 +83,131 @@ export const apiGetChannelByName = async (teamName, channelName) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Add user to channel.
|
||||
* See https://api.mattermost.com/#tag/channels/paths/~1channels~1{channel_id}~1members/post
|
||||
* @param {string} userId - The ID of user to add into the channel
|
||||
* @param {string} channelId - The channel ID
|
||||
* @return {Object} returns {member} on success or {error, status} on error
|
||||
* Create a group message channel.
|
||||
* See https://api.mattermost.com/#operation/CreateGroupChannel
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {Array} userIds - user IDs to be in the group message channel
|
||||
* @return {Object} returns {channel} on success or {error, status} on error
|
||||
*/
|
||||
export const apiAddUserToChannel = async (userId, channelId) => {
|
||||
export const apiCreateGroupChannel = async (baseUrl, userIds = []) => {
|
||||
try {
|
||||
const response = await client.post(
|
||||
`/api/v4/channels/${channelId}/members`,
|
||||
{user_id: userId},
|
||||
`${baseUrl}/api/v4/channels/group`,
|
||||
userIds,
|
||||
);
|
||||
|
||||
return {member: response.data};
|
||||
return {channel: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a channel by name.
|
||||
* See https://api.mattermost.com/#operation/GetChannelByName
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} teamId - team ID
|
||||
* @param {string} channelName - channel name
|
||||
* @return {Object} returns {channel} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetChannelByName = async (baseUrl, teamId, channelName) => {
|
||||
try {
|
||||
const response = await client.get(`${baseUrl}/api/v4/teams/${teamId}/channels/name/${channelName}`);
|
||||
|
||||
return {channel: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a channel by name and team name.
|
||||
* See https://api.mattermost.com/#operation/GetChannelByNameForTeamName
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} teamName - team name
|
||||
* @param {string} channelName - channel name
|
||||
* @return {Object} returns {channel} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetChannelByNameAndTeamName = async (baseUrl, teamName, channelName) => {
|
||||
try {
|
||||
const response = await client.get(`${baseUrl}/api/v4/teams/name/${teamName}/channels/name/${channelName}`);
|
||||
|
||||
return {channel: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get channels for user.
|
||||
* See https://api.mattermost.com/#operation/GetChannelsForTeamForUser
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - The user ID
|
||||
* @param {string} teamId - The team ID the user belongs to
|
||||
* @return {Object} returns {channels} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetChannelsForUser = async (baseUrl, userId, teamId) => {
|
||||
try {
|
||||
const response = await client.get(`${baseUrl}/api/v4/users/${userId}/teams/${teamId}/channels`);
|
||||
|
||||
return {channels: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get unread messages.
|
||||
* See https://api.mattermost.com/#operation/GetChannelUnread
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - The user ID to perform view actions for
|
||||
* @param {string} channelId - The channel ID that is being viewed
|
||||
* @return {Object} returns response on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetUnreadMessages = async (baseUrl, userId, channelId) => {
|
||||
try {
|
||||
return await client.get(`${baseUrl}/api/v4/users/${userId}/channels/${channelId}/unread`);
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove user from channel.
|
||||
* See https://api.mattermost.com/#operation/RemoveUserFromChannel
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} channelId - The channel ID
|
||||
* @param {string} userId - The user ID to be removed from channel
|
||||
* @return {Object} returns {status} on success or {error, status} on error
|
||||
*/
|
||||
export const apiRemoveUserFromChannel = async (baseUrl, channelId, userId) => {
|
||||
try {
|
||||
const response = await client.delete(
|
||||
`${baseUrl}/api/v4/channels/${channelId}/members/${userId}`,
|
||||
);
|
||||
|
||||
return {status: response.status};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* View channel.
|
||||
* See https://api.mattermost.com/#operation/ViewChannel
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - The user ID to perform view actions for
|
||||
* @param {string} channelId - The channel ID that is being viewed
|
||||
* @return {Object} returns {viewed} on success or {error, status} on error
|
||||
*/
|
||||
export const apiViewChannel = async (baseUrl, userId, channelId) => {
|
||||
try {
|
||||
const response = await client.post(
|
||||
`${baseUrl}/api/v4/channels/members/${userId}/view`,
|
||||
{channel_id: channelId},
|
||||
);
|
||||
|
||||
return {viewed: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
@@ -93,7 +229,13 @@ function generateRandomChannel(teamId, type, prefix) {
|
||||
export const Channel = {
|
||||
apiAddUserToChannel,
|
||||
apiCreateChannel,
|
||||
apiCreateDirectChannel,
|
||||
apiCreateGroupChannel,
|
||||
apiGetChannelByName,
|
||||
apiGetChannelsForUser,
|
||||
apiGetUnreadMessages,
|
||||
apiRemoveUserFromChannel,
|
||||
apiViewChannel,
|
||||
};
|
||||
|
||||
export default Channel;
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
import testConfig from '../test_config';
|
||||
|
||||
export const client = axios.create({
|
||||
baseURL: testConfig.siteUrl,
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
});
|
||||
|
||||
|
||||
@@ -35,13 +35,11 @@ export const apiUploadFile = async (name, absFilePath, requestOptions = {}) => {
|
||||
formData.append(name, fs.createReadStream(absFilePath));
|
||||
|
||||
try {
|
||||
const response = await client.request({
|
||||
return await client.request({
|
||||
...requestOptions,
|
||||
data: formData,
|
||||
headers: formData.getHeaders(),
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
"EnableUserCreation": true,
|
||||
"EnableOpenServer": true,
|
||||
"EnableUserDeactivation": false,
|
||||
"EnableCustomUserStatuses": false,
|
||||
"RestrictCreationToDomains": "",
|
||||
"EnableCustomBrand": false,
|
||||
"CustomBrandText": "",
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Bot from './bot';
|
||||
import Channel from './channel';
|
||||
import Ldap from './ldap';
|
||||
import Plugin from './plugin';
|
||||
import Post from './post';
|
||||
import Preference from './preference';
|
||||
import Setup from './setup';
|
||||
import Status from './status';
|
||||
import System from './system';
|
||||
import Team from './team';
|
||||
import User from './user';
|
||||
|
||||
export {
|
||||
Bot,
|
||||
Channel,
|
||||
Ldap,
|
||||
Plugin,
|
||||
Post,
|
||||
Preference,
|
||||
Setup,
|
||||
Status,
|
||||
System,
|
||||
Team,
|
||||
User,
|
||||
|
||||
@@ -20,14 +20,13 @@ import {getResponseFromError} from './common';
|
||||
|
||||
/**
|
||||
* Synchronize any user attribute changes in the configured AD/LDAP server with Mattermost.
|
||||
* See https://api.mattermost.com/#tag/LDAP/paths/~1ldap~1sync/post
|
||||
* @return {string} returns {status} on success or {error, status} on error
|
||||
* See https://api.mattermost.com/#operation/SyncLdap
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @return {string} returns response on success or {error, status} on error
|
||||
*/
|
||||
export const apiLDAPSync = async () => {
|
||||
export const apiLDAPSync = async (baseUrl) => {
|
||||
try {
|
||||
const response = await client.post('/api/v4/ldap/sync');
|
||||
|
||||
return response;
|
||||
return await client.post(`${baseUrl}/api/v4/ldap/sync`);
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
@@ -35,14 +34,15 @@ export const apiLDAPSync = async () => {
|
||||
|
||||
/**
|
||||
* Test the current AD/LDAP configuration to see if the AD/LDAP server can be contacted successfully.
|
||||
* See https://api.mattermost.com/#tag/LDAP/paths/~1ldap~1test/post
|
||||
* @return {string} returns {status} on success or {error, status} on error
|
||||
* See https://api.mattermost.com/#operation/TestLdap
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @return {Object} returns {status} on success or {error, status} on error
|
||||
*/
|
||||
export const apiLDAPTest = async () => {
|
||||
export const apiLDAPTest = async (baseUrl) => {
|
||||
try {
|
||||
const response = await client.post('/api/v4/ldap/test');
|
||||
const response = await client.post(`${baseUrl}/api/v4/ldap/test`);
|
||||
|
||||
return response.data;
|
||||
return {status: response.status};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
@@ -50,12 +50,13 @@ export const apiLDAPTest = async () => {
|
||||
|
||||
/**
|
||||
* Check that LDAP server can connect and is synchronized with Mattermost server.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
*/
|
||||
export const apiRequireLDAPServer = async () => {
|
||||
const {error: testError} = await apiLDAPTest();
|
||||
export const apiRequireLDAPServer = async (baseUrl) => {
|
||||
const {error: testError} = await apiLDAPTest(baseUrl);
|
||||
jestExpect(testError).toBeUndefined();
|
||||
|
||||
const {error: syncError} = await apiLDAPSync();
|
||||
const {error: syncError} = await apiLDAPSync(baseUrl);
|
||||
jestExpect(syncError).toBeUndefined();
|
||||
};
|
||||
|
||||
|
||||
158
detox/e2e/support/server_api/plugin.js
Normal file
158
detox/e2e/support/server_api/plugin.js
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import client from './client';
|
||||
import {apiUploadFile, getResponseFromError} from './common';
|
||||
|
||||
// ****************************************************************
|
||||
// Plugins
|
||||
// https://api.mattermost.com/#tag/plugins
|
||||
//
|
||||
// Exported API function should have the following:
|
||||
// - documented using JSDoc
|
||||
// - meaningful description
|
||||
// - match the referenced API endpoints
|
||||
// - parameter/s defined by `@param`
|
||||
// - return value defined by `@return`
|
||||
// ****************************************************************
|
||||
|
||||
const prepackagedPlugins = [
|
||||
'antivirus',
|
||||
'mattermost-autolink',
|
||||
'com.mattermost.aws-sns',
|
||||
'com.mattermost.plugin-channel-export',
|
||||
'com.mattermost.custom-attributes',
|
||||
'github',
|
||||
'com.github.manland.mattermost-plugin-gitlab',
|
||||
'com.mattermost.plugin-incident-management',
|
||||
'jenkins',
|
||||
'jira',
|
||||
'com.mattermost.nps',
|
||||
'com.mattermost.welcomebot',
|
||||
'zoom',
|
||||
];
|
||||
|
||||
/**
|
||||
* Disable non-prepackaged plugins.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
*/
|
||||
export const apiDisableNonPrepackagedPlugins = async (baseUrl) => {
|
||||
const {plugins} = await apiGetAllPlugins(baseUrl);
|
||||
if (!plugins) {
|
||||
return;
|
||||
}
|
||||
plugins.active.forEach(async (plugin) => {
|
||||
if (!prepackagedPlugins.includes(plugin.id)) {
|
||||
await apiDisablePluginById(baseUrl, plugin.id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Disable plugin.
|
||||
* See https://api.mattermost.com/#operation/DisablePlugin
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} pluginId - the plugin ID
|
||||
* @return {Object} returns response on success or {error, status} on error
|
||||
*/
|
||||
export const apiDisablePluginById = async (baseUrl, pluginId) => {
|
||||
try {
|
||||
return await client.post(`${baseUrl}/api/v4/plugins/${encodeURIComponent(pluginId)}/disable`);
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable plugin.
|
||||
* See https://api.mattermost.com/#operation/EnablePlugin
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} pluginId - the plugin ID
|
||||
* @return {Object} returns response on success or {error, status} on error
|
||||
*/
|
||||
export const apiEnablePluginById = async (baseUrl, pluginId) => {
|
||||
try {
|
||||
return await client.post(`${baseUrl}/api/v4/plugins/${encodeURIComponent(pluginId)}/enable`);
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get plugins.
|
||||
* See https://api.mattermost.com/#operation/GetPlugins
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @return {Object} returns {plugins} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetAllPlugins = async (baseUrl) => {
|
||||
try {
|
||||
const response = await client.get(`${baseUrl}/api/v4/plugins`);
|
||||
|
||||
return {plugins: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Install plugin from URL.
|
||||
* See https://api.mattermost.com/#operation/InstallPluginFromUrl
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} pluginDownloadUrl - URL used to download the plugin
|
||||
* @param {string} force - Set to 'true' to overwrite a previously installed plugin with the same ID, if any
|
||||
* @return {Object} returns {plugin} on success or {error, status} on error
|
||||
*/
|
||||
export const apiInstallPluginFromUrl = async (baseUrl, pluginDownloadUrl, force = false) => {
|
||||
try {
|
||||
const response = await client.post(`${baseUrl}/api/v4/plugins/install_from_url?plugin_download_url=${encodeURIComponent(pluginDownloadUrl)}&force=${force}`);
|
||||
|
||||
return {plugin: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove plugin.
|
||||
* See https://api.mattermost.com/#operation/RemovePlugin
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} pluginId - the plugin ID
|
||||
* @return {Object} returns response on success or {error, status} on error
|
||||
*/
|
||||
export const apiRemovePluginById = async (baseUrl, pluginId) => {
|
||||
try {
|
||||
return await client.delete(`${baseUrl}/api/v4/plugins/${encodeURIComponent(pluginId)}`);
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload plugin.
|
||||
* See https://api.mattermost.com/#operation/UploadPlugin
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} filename - the filename of plugin to be uploaded
|
||||
* @return {Object} returns response on success or {error, status} on error
|
||||
*/
|
||||
export const apiUploadPlugin = async (baseUrl, filename) => {
|
||||
try {
|
||||
const absFilePath = path.resolve(__dirname, `../../support/fixtures/${filename}`);
|
||||
return await apiUploadFile('plugin', absFilePath, {url: `${baseUrl}/api/v4/plugins`, method: 'POST'});
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const Plugin = {
|
||||
apiDisableNonPrepackagedPlugins,
|
||||
apiDisablePluginById,
|
||||
apiEnablePluginById,
|
||||
apiGetAllPlugins,
|
||||
apiInstallPluginFromUrl,
|
||||
apiRemovePluginById,
|
||||
apiUploadPlugin,
|
||||
};
|
||||
|
||||
export default Plugin;
|
||||
@@ -5,8 +5,8 @@ import client from './client';
|
||||
import {getResponseFromError} from './common';
|
||||
|
||||
// ****************************************************************
|
||||
// Channels
|
||||
// See https://api.mattermost.com/#tag/channels
|
||||
// Posts
|
||||
// See https://api.mattermost.com/#tag/posts
|
||||
//
|
||||
// Exported API function should have the following:
|
||||
// - documented using JSDoc
|
||||
@@ -16,15 +16,44 @@ import {getResponseFromError} from './common';
|
||||
// - return value defined by `@return`
|
||||
// ****************************************************************
|
||||
|
||||
/**
|
||||
* Create a new post in a channel. To create the post as a comment on another post, provide root_id.
|
||||
* See https://api.mattermost.com/#operation/CreatePost
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} option.channelId - The channel ID to post in
|
||||
* @param {string} option.message - The message contents, can be formatted with Markdown
|
||||
* @param {string} option.rootId - The post ID to comment on
|
||||
* @param {Object} option.props - A general object property bag to attach to the post
|
||||
* @param {Date} option.createAt - The date the post is created at
|
||||
* @return {Object} returns {post} on success or {error, status} on error
|
||||
*/
|
||||
export const apiCreatePost = async (baseUrl, {channelId, message, rootId, props = {}, createAt = 0}) => {
|
||||
try {
|
||||
const payload = {
|
||||
channel_id: channelId,
|
||||
message,
|
||||
root_id: rootId,
|
||||
props,
|
||||
create_at: createAt,
|
||||
};
|
||||
const response = await client.post(`${baseUrl}/api/v4/posts`, payload);
|
||||
|
||||
return {post: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get posts for a channel.
|
||||
* See https://api.mattermost.com/#tag/posts/paths/~1channels~1{channel_id}~1posts/get
|
||||
* See https://api.mattermost.com/#operation/GetPostsForChannel
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} channelId - The channel ID to get the posts for
|
||||
* @return {Object} returns {posts} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetPostsInChannel = async (channelId) => {
|
||||
export const apiGetPostsInChannel = async (baseUrl, channelId) => {
|
||||
try {
|
||||
const response = await client.get(`/api/v4/channels/${channelId}/posts`);
|
||||
const response = await client.get(`${baseUrl}/api/v4/channels/${channelId}/posts`);
|
||||
|
||||
const {order, posts} = response.data;
|
||||
|
||||
@@ -38,17 +67,41 @@ export const apiGetPostsInChannel = async (channelId) => {
|
||||
|
||||
/**
|
||||
* Get last post in a channel.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} channelId - The channel ID to get the last post
|
||||
* @return {Object} returns {post} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetLastPostInChannel = async (channelId) => {
|
||||
const {posts} = await apiGetPostsInChannel(channelId);
|
||||
export const apiGetLastPostInChannel = async (baseUrl, channelId) => {
|
||||
const {posts} = await apiGetPostsInChannel(baseUrl, channelId);
|
||||
return {post: posts[0]};
|
||||
};
|
||||
|
||||
/**
|
||||
* Patch a post.
|
||||
* See https://api.mattermost.com/#operation/PatchPost
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} postId - the post ID
|
||||
* @param {Object} postData - data to partially update a post
|
||||
* @return {Object} returns {post} on success or {error, status} on error
|
||||
*/
|
||||
export const apiPatchPost = async (baseUrl, postId, postData) => {
|
||||
try {
|
||||
const response = await client.put(
|
||||
`${baseUrl}/api/v4/posts/${postId}/patch`,
|
||||
postData,
|
||||
);
|
||||
|
||||
return {post: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const Post = {
|
||||
apiCreatePost,
|
||||
apiGetLastPostInChannel,
|
||||
apiGetPostsInChannel,
|
||||
apiPatchPost,
|
||||
};
|
||||
|
||||
export default Post;
|
||||
|
||||
101
detox/e2e/support/server_api/preference.js
Normal file
101
detox/e2e/support/server_api/preference.js
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import client from './client';
|
||||
import {getResponseFromError} from './common';
|
||||
|
||||
// ****************************************************************
|
||||
// Preferences
|
||||
// See https://api.mattermost.com/#tag/preferences
|
||||
//
|
||||
// Exported API function should have the following:
|
||||
// - documented using JSDoc
|
||||
// - meaningful description
|
||||
// - match the referenced API endpoints
|
||||
// - parameter/s defined by `@param`
|
||||
// - return value defined by `@return`
|
||||
// ****************************************************************
|
||||
|
||||
/**
|
||||
* Save the user's favorite channel preference.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - the user ID
|
||||
* @param {string} channelId - the channel id to be favorited
|
||||
* @return {string} returns {status} on success or {error, status} on error
|
||||
*/
|
||||
export const apiSaveFavoriteChannelPreference = (baseUrl, userId, channelId) => {
|
||||
const preference = {
|
||||
user_id: userId,
|
||||
category: 'favorite_channel',
|
||||
name: channelId,
|
||||
value: 'true',
|
||||
};
|
||||
|
||||
return apiSaveUserPreferences(baseUrl, userId, [preference]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Save the user's teammate name display preference.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - the user ID
|
||||
* @param {string} nameFormat - one of "username" (default), "nickname_full_name" or "full_name"
|
||||
* @returns
|
||||
*/
|
||||
export const apiSaveTeammateNameDisplayPreference = (baseUrl, userId, nameFormat = 'username') => {
|
||||
const preference = {
|
||||
user_id: userId,
|
||||
category: 'display_settings',
|
||||
name: 'name_format',
|
||||
value: nameFormat,
|
||||
};
|
||||
|
||||
return apiSaveUserPreferences(baseUrl, userId, [preference]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Save the user's teams order preference.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - the user ID
|
||||
* @param {Array} orderedTeamIds - ordered array of team IDs
|
||||
* @return {string} returns {status} on success or {error, status} on error
|
||||
*/
|
||||
export const apiSaveTeamsOrderPreference = (baseUrl, userId, orderedTeamIds = []) => {
|
||||
const preference = {
|
||||
user_id: userId,
|
||||
category: 'teams_order',
|
||||
name: '',
|
||||
value: orderedTeamIds.toString(),
|
||||
};
|
||||
|
||||
return apiSaveUserPreferences(baseUrl, userId, [preference]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Save the user's preferences.
|
||||
* See https://api.mattermost.com/#operation/UpdatePreferences
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - the user ID
|
||||
* @param {Array} preferences - a list of user's preferences
|
||||
* @return {string} returns {status} on success or {error, status} on error
|
||||
*/
|
||||
export const apiSaveUserPreferences = async (baseUrl, userId, preferences = []) => {
|
||||
try {
|
||||
const response = await client.put(
|
||||
`${baseUrl}/api/v4/users/${userId}/preferences`,
|
||||
preferences,
|
||||
);
|
||||
|
||||
return {status: response.status};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const Preference = {
|
||||
apiSaveFavoriteChannelPreference,
|
||||
apiSaveTeammateNameDisplayPreference,
|
||||
apiSaveTeamsOrderPreference,
|
||||
apiSaveUserPreferences,
|
||||
};
|
||||
|
||||
export default Preference;
|
||||
@@ -7,20 +7,21 @@ import User from './user';
|
||||
|
||||
/**
|
||||
* Creates new user, channel and team for test isolation.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {Object} options - may pass options to predefine channel, team and user creation
|
||||
* @return {Object} returns {channel, team, user} on success or {error, status} on error
|
||||
*/
|
||||
export const apiInit = async ({
|
||||
export const apiInit = async (baseUrl, {
|
||||
channelOptions = {type: 'O', prefix: 'channel'},
|
||||
teamOptions = {type: 'O', prefix: 'team'},
|
||||
userOptions = {prefix: 'user'},
|
||||
} = {}) => {
|
||||
const {team} = await Team.apiCreateTeam(teamOptions);
|
||||
const {channel} = await Channel.apiCreateChannel({...channelOptions, teamId: team.id});
|
||||
const {user} = await User.apiCreateUser(userOptions);
|
||||
const {team} = await Team.apiCreateTeam(baseUrl, teamOptions);
|
||||
const {channel} = await Channel.apiCreateChannel(baseUrl, {...channelOptions, teamId: team.id});
|
||||
const {user} = await User.apiCreateUser(baseUrl, userOptions);
|
||||
|
||||
await Team.apiAddUserToTeam(user.id, team.id);
|
||||
await Channel.apiAddUserToChannel(user.id, channel.id);
|
||||
await Team.apiAddUserToTeam(baseUrl, user.id, team.id);
|
||||
await Channel.apiAddUserToChannel(baseUrl, user.id, channel.id);
|
||||
|
||||
return {
|
||||
channel,
|
||||
|
||||
62
detox/e2e/support/server_api/status.js
Normal file
62
detox/e2e/support/server_api/status.js
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import client from './client';
|
||||
import {getResponseFromError} from './common';
|
||||
|
||||
// ****************************************************************
|
||||
// Status
|
||||
// See https://api.mattermost.com/#tag/status
|
||||
//
|
||||
// Exported API function should have the following:
|
||||
// - documented using JSDoc
|
||||
// - meaningful description
|
||||
// - match the referenced API endpoints
|
||||
// - parameter/s defined by `@param`
|
||||
// - return value defined by `@return`
|
||||
// ****************************************************************
|
||||
|
||||
/**
|
||||
* Get user status.
|
||||
* See https://api.mattermost.com/#operation/GetUserStatus
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - the user ID
|
||||
* @return {Object} returns {userStatus} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetUserStatus = async (baseUrl, userId) => {
|
||||
try {
|
||||
const response = await client.get(`${baseUrl}/api/v4/users/${userId}/status`);
|
||||
|
||||
return {userStatus: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user status.
|
||||
* See https://api.mattermost.com/#operation/UpdateUserStatus
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - the user ID
|
||||
* @param {string} status - the user status, can be online, away, offline and dnd
|
||||
* @return {Object} returns {userStatus} on success or {error, status} on error
|
||||
*/
|
||||
export const apiUpdateUserStatus = async (baseUrl, userId, status = 'online') => {
|
||||
try {
|
||||
const response = await client.put(
|
||||
`${baseUrl}/api/v4/users/${userId}/status`,
|
||||
{user_id: userId, status},
|
||||
);
|
||||
|
||||
return {userStatus: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const Status = {
|
||||
apiGetUserStatus,
|
||||
apiUpdateUserStatus,
|
||||
};
|
||||
|
||||
export default Status;
|
||||
@@ -27,73 +27,38 @@ import defaultServerConfig from './default_config.json';
|
||||
|
||||
/**
|
||||
* Check system health.
|
||||
* See https://api.mattermost.com/#tag/system/paths/~1system~1ping/get
|
||||
* @return {Object} returns {data} on success or {error, status} on error
|
||||
* @param {string} baseUrl - the base server URL
|
||||
*/
|
||||
export const apiCheckSystemHealth = async () => {
|
||||
try {
|
||||
const response = await client.get('/api/v4/system/ping?get_server_status=true');
|
||||
return {data: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
export const apiCheckSystemHealth = async (baseUrl) => {
|
||||
const {data} = await apiPingServerStatus(baseUrl);
|
||||
jestExpect(data.status).toEqual('OK');
|
||||
jestExpect(data.database_status).toEqual('OK');
|
||||
jestExpect(data.filestore_status).toEqual('OK');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get configuration.
|
||||
* See https://api.mattermost.com/#tag/system/paths/~1config/get
|
||||
* Send a test email.
|
||||
* See https://api.mattermost.com/#operation/TestEmail
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @return {Object} returns response on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetConfig = async () => {
|
||||
export const apiEmailTest = async (baseUrl) => {
|
||||
try {
|
||||
const response = await client.get('/api/v4/config');
|
||||
|
||||
return {config: response.data};
|
||||
return await client.post(`${baseUrl}/api/v4/email/test`);
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update configuration.
|
||||
* See https://api.mattermost.com/#tag/system/paths/~1config/put
|
||||
* @param {Object} newConfig - specific config to update
|
||||
*/
|
||||
export const apiUpdateConfig = async (newConfig = {}) => {
|
||||
try {
|
||||
const {config: currentConfig} = await apiGetConfig();
|
||||
const config = merge.all([currentConfig, getDefaultConfig(), newConfig]);
|
||||
|
||||
const response = await client.put(
|
||||
'/api/v4/config',
|
||||
config,
|
||||
);
|
||||
|
||||
return {config: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
function getDefaultConfig() {
|
||||
const fromEnv = {
|
||||
LdapSettings: {
|
||||
LdapServer: testConfig.ldapServer,
|
||||
LdapPort: testConfig.ldapPort,
|
||||
},
|
||||
ServiceSettings: {SiteURL: testConfig.siteUrl},
|
||||
};
|
||||
|
||||
return merge(defaultServerConfig, fromEnv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get client license.
|
||||
* See https://api.mattermost.com/#tag/system/paths/~1license~1client/get
|
||||
* See https://api.mattermost.com/#operation/GetClientLicense
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @return {Object} returns {license} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetClientLicense = async () => {
|
||||
export const apiGetClientLicense = async (baseUrl) => {
|
||||
try {
|
||||
const response = await client.get('/api/v4/license/client?format=old');
|
||||
const response = await client.get(`${baseUrl}/api/v4/license/client?format=old`);
|
||||
|
||||
return {license: response.data};
|
||||
} catch (err) {
|
||||
@@ -101,12 +66,44 @@ export const apiGetClientLicense = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get configuration.
|
||||
* See https://api.mattermost.com/#operation/GetConfig
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @return {Object} returns {config} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetConfig = async (baseUrl) => {
|
||||
try {
|
||||
const response = await client.get(`${baseUrl}/api/v4/config`);
|
||||
|
||||
return {config: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ping server status.
|
||||
* See https://api.mattermost.com/#operation/GetPing
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @return {Object} returns {data} on success or {error, status} on error
|
||||
*/
|
||||
export const apiPingServerStatus = async (baseUrl) => {
|
||||
try {
|
||||
const response = await client.get(`${baseUrl}/api/v4/system/ping?get_server_status=true`);
|
||||
return {data: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Require server license to successfully continue.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @return {Object} returns {license} on success or fail when no license
|
||||
*/
|
||||
export const apiRequireLicense = async () => {
|
||||
const {license} = await getClientLicense();
|
||||
export const apiRequireLicense = async (baseUrl) => {
|
||||
const {license} = await getClientLicense(baseUrl);
|
||||
|
||||
if (license.IsLicensed !== 'true') {
|
||||
console.error('Server has no Enterprise license.');
|
||||
@@ -118,11 +115,12 @@ export const apiRequireLicense = async () => {
|
||||
|
||||
/**
|
||||
* Require server license with specific feature to successfully continue.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} key - feature, e.g. LDAP
|
||||
* @return {Object} returns {license} on success or fail when no license or no license to specific feature.
|
||||
*/
|
||||
export const apiRequireLicenseForFeature = async (key = '') => {
|
||||
const {license} = await getClientLicense();
|
||||
export const apiRequireLicenseForFeature = async (baseUrl, key = '') => {
|
||||
const {license} = await getClientLicense(baseUrl);
|
||||
|
||||
if (license.IsLicensed !== 'true') {
|
||||
console.error('Server has no Enterprise license.');
|
||||
@@ -146,44 +144,93 @@ export const apiRequireLicenseForFeature = async (key = '') => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload server license with file expected at "/detox/e2e/support/fixtures/mattermost-license.txt"
|
||||
* Require SMTP server to be running.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
*/
|
||||
export const apiUploadLicense = async () => {
|
||||
const absFilePath = path.resolve(__dirname, '../../support/fixtures/mattermost-license.txt');
|
||||
const response = await apiUploadFile('license', absFilePath, {url: '/api/v4/license', method: 'POST'});
|
||||
export const apiRequireSMTPServer = async (baseUrl) => {
|
||||
const {status} = await apiEmailTest(baseUrl);
|
||||
jestExpect(status).toEqual(200);
|
||||
};
|
||||
|
||||
return response;
|
||||
/**
|
||||
* Update configuration.
|
||||
* See https://api.mattermost.com/#operation/UpdateConfig
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {Object} newConfig - specific config to update
|
||||
* @return {Object} returns {config} on success or {error, status} on error
|
||||
*/
|
||||
export const apiUpdateConfig = async (baseUrl, newConfig = {}) => {
|
||||
try {
|
||||
const {config: currentConfig} = await apiGetConfig(baseUrl);
|
||||
const config = merge.all([currentConfig, getDefaultConfig(), newConfig]);
|
||||
|
||||
const response = await client.put(
|
||||
`${baseUrl}/api/v4/config`,
|
||||
config,
|
||||
);
|
||||
|
||||
return {config: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload server license with file expected at "/detox/e2e/support/fixtures/mattermost-license.txt"
|
||||
* See https://api.mattermost.com/#operation/UploadLicenseFile
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @return {Object} returns response on success or {error, status} on error
|
||||
*/
|
||||
export const apiUploadLicense = async (baseUrl) => {
|
||||
const absFilePath = path.resolve(__dirname, '../../support/fixtures/mattermost-license.txt');
|
||||
return apiUploadFile('license', absFilePath, {url: `${baseUrl}/api/v4/license`, method: 'POST'});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get client license.
|
||||
* If no license, try to upload if license file is available at "/support/fixtures/mattermost-license.txt".
|
||||
* @return {Object} returns {license} on success or upload when no license or get updated license.
|
||||
*/
|
||||
async function getClientLicense() {
|
||||
const {license} = await apiGetClientLicense();
|
||||
async function getClientLicense(baseUrl) {
|
||||
const {license} = await apiGetClientLicense(baseUrl);
|
||||
if (license.IsLicensed === 'true') {
|
||||
return {license};
|
||||
}
|
||||
|
||||
// Upload a license if server is currently not loaded with license
|
||||
const response = await apiUploadLicense();
|
||||
const response = await apiUploadLicense(baseUrl);
|
||||
if (response.error) {
|
||||
console.warn(response.error.message);
|
||||
return {license};
|
||||
}
|
||||
|
||||
// Get an updated client license
|
||||
const out = await apiGetClientLicense();
|
||||
const out = await apiGetClientLicense(baseUrl);
|
||||
return {license: out.license};
|
||||
}
|
||||
|
||||
function getDefaultConfig() {
|
||||
const fromEnv = {
|
||||
LdapSettings: {
|
||||
LdapServer: testConfig.ldapServer,
|
||||
LdapPort: testConfig.ldapPort,
|
||||
},
|
||||
ServiceSettings: {SiteURL: testConfig.siteUrl},
|
||||
};
|
||||
|
||||
return merge(defaultServerConfig, fromEnv);
|
||||
}
|
||||
|
||||
export const System = {
|
||||
apiCheckSystemHealth,
|
||||
apiGetConfig,
|
||||
apiUpdateConfig,
|
||||
apiEmailTest,
|
||||
apiGetClientLicense,
|
||||
apiGetConfig,
|
||||
apiPingServerStatus,
|
||||
apiRequireLicense,
|
||||
apiRequireLicenseForFeature,
|
||||
apiRequireSMTPServer,
|
||||
apiUpdateConfig,
|
||||
apiUploadLicense,
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {capitalize, getRandomId} from '@support/utils';
|
||||
import jestExpect from 'expect';
|
||||
|
||||
import client from './client';
|
||||
import {getResponseFromError} from './common';
|
||||
@@ -18,38 +19,18 @@ import {getResponseFromError} from './common';
|
||||
// - return value defined by `@return`
|
||||
// ****************************************************************
|
||||
|
||||
/**
|
||||
* Create a team.
|
||||
* See https://api.mattermost.com/#tag/teams/paths/~1teams/post
|
||||
* @param {string} option.type - 'O' (default) for open, 'I' for invite only
|
||||
* @param {string} option.prefix - option to add prefix to name and display name
|
||||
* @param {Object} team - fix team object to be created
|
||||
* @return {Object} returns {team} on success or {error, status} on error
|
||||
*/
|
||||
export const apiCreateTeam = async ({type = 'O', prefix = 'team', team = null} = {}) => {
|
||||
try {
|
||||
const response = await client.post(
|
||||
'/api/v4/teams',
|
||||
team || generateRandomTeam(type, prefix),
|
||||
);
|
||||
|
||||
return {team: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add user to team.
|
||||
* See https://api.mattermost.com/#tag/teams/paths/~1teams~1{team_id}~1members/post
|
||||
* See https://api.mattermost.com/#operation/AddTeamMember
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - The ID of user to add into the team
|
||||
* @param {string} teamId - The team ID
|
||||
* @return {Object} returns {member} on success or {error, status} on error
|
||||
*/
|
||||
export const apiAddUserToTeam = async (userId, teamId) => {
|
||||
export const apiAddUserToTeam = async (baseUrl, userId, teamId) => {
|
||||
try {
|
||||
const response = await client.post(
|
||||
`/api/v4/teams/${teamId}/members`,
|
||||
`${baseUrl}/api/v4/teams/${teamId}/members`,
|
||||
{team_id: teamId, user_id: userId},
|
||||
);
|
||||
|
||||
@@ -60,14 +41,92 @@ export const apiAddUserToTeam = async (userId, teamId) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Get team members for user.
|
||||
* See https://api.mattermost.com/#tag/teams/paths/~1users~1{user_id}~1teams~1members/get
|
||||
* @param {string} userId
|
||||
* Create a team.
|
||||
* See https://api.mattermost.com/#operation/CreateTeam
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} option.type - 'O' (default) for open, 'I' for invite only
|
||||
* @param {string} option.prefix - prefix to name and display name
|
||||
* @param {Object} option.team - team object to be created
|
||||
* @return {Object} returns {team} on success or {error, status} on error
|
||||
*/
|
||||
export const apiCreateTeam = async (baseUrl, {type = 'O', prefix = 'team', team = null} = {}) => {
|
||||
try {
|
||||
const response = await client.post(
|
||||
`${baseUrl}/api/v4/teams`,
|
||||
team || generateRandomTeam(type, prefix),
|
||||
);
|
||||
|
||||
return {team: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a team.
|
||||
* See https://api.mattermost.com/#operation/SoftDeleteTeam
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} teamId - The team ID
|
||||
* @return {Object} returns {status} on success or {error, status} on error
|
||||
*/
|
||||
export const apiDeleteTeam = async (baseUrl, teamId) => {
|
||||
try {
|
||||
const response = await client.delete(
|
||||
`${baseUrl}/api/v4/teams/${teamId}`,
|
||||
);
|
||||
|
||||
return {status: response.status};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete teams.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {Array} teams - array of teams
|
||||
*/
|
||||
export const apiDeleteTeams = async (baseUrl, teams = []) => {
|
||||
let teamArray = teams;
|
||||
if (!teamArray.length > 0) {
|
||||
({teams: teamArray} = await Team.apiGetTeams(baseUrl));
|
||||
}
|
||||
|
||||
teamArray.forEach(async (team) => {
|
||||
const {status} = await Team.apiDeleteTeam(baseUrl, team.id);
|
||||
jestExpect(status).toEqual(200);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove user from team.
|
||||
* See https://api.mattermost.com/#operation/RemoveTeamMember
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} teamId - The team ID
|
||||
* @param {string} userId - The user ID to be removed from team
|
||||
* @return {Object} returns {status} on success or {error, status} on error
|
||||
*/
|
||||
export const apiDeleteUserFromTeam = async (baseUrl, teamId, userId) => {
|
||||
try {
|
||||
const response = await client.delete(
|
||||
`${baseUrl}/api/v4/teams/${teamId}/members/${userId}`,
|
||||
);
|
||||
|
||||
return {status: response.status};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get teams.
|
||||
* See https://api.mattermost.com/#operation/GetAllTeams
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @return {Object} returns {teams} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetTeamMembersForUser = async (userId = 'me') => {
|
||||
export const apiGetTeams = async (baseUrl) => {
|
||||
try {
|
||||
const response = await client.get(`/api/v4/users/${userId}/teams`);
|
||||
const response = await client.get(`${baseUrl}/api/v4/teams`);
|
||||
|
||||
return {teams: response.data};
|
||||
} catch (err) {
|
||||
@@ -75,6 +134,71 @@ export const apiGetTeamMembersForUser = async (userId = 'me') => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get teams for user.
|
||||
* See https://api.mattermost.com/#operation/GetTeamsForUser
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - The user ID
|
||||
* @return {Object} returns {teams} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetTeamsForUser = async (baseUrl, userId = 'me') => {
|
||||
try {
|
||||
const response = await client.get(`${baseUrl}/api/v4/users/${userId}/teams`);
|
||||
|
||||
return {teams: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Patch a team.
|
||||
* See https://api.mattermost.com/#operation/PatchTeam
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} teamId - The team ID
|
||||
* @param {string} patch.display_name - Display name
|
||||
* @param {string} patch.description - Description
|
||||
* @param {string} patch.company_name - Company name
|
||||
* @param {string} patch.allowed_domains - Allowed domains
|
||||
* @param {boolean} patch.allow_open_invite - Allow open invite
|
||||
* @param {boolean} patch.group_constrained - Group constrained
|
||||
* @return {Object} returns {team} on success or {error, status} on error
|
||||
*/
|
||||
export const apiPatchTeam = async (baseUrl, teamId, teamData) => {
|
||||
try {
|
||||
const response = await client.put(
|
||||
`${baseUrl}/api/v4/teams/${teamId}/patch`,
|
||||
teamData,
|
||||
);
|
||||
|
||||
return {team: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Patch teams.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} patch.display_name - Display name
|
||||
* @param {string} patch.description - Description
|
||||
* @param {string} patch.company_name - Company name
|
||||
* @param {string} patch.allowed_domains - Allowed domains
|
||||
* @param {boolean} patch.allow_open_invite - Allow open invite
|
||||
* @param {boolean} patch.group_constrained - Group constrained
|
||||
* @param {Array} teams - array of teams
|
||||
*/
|
||||
export const apiPatchTeams = async (baseUrl, teamData, teams = []) => {
|
||||
let teamArray = teams;
|
||||
if (!teamArray.length > 0) {
|
||||
({teams: teamArray} = await Team.apiGetTeams(baseUrl));
|
||||
}
|
||||
|
||||
teamArray.forEach(async (team) => {
|
||||
await Team.apiPatchTeam(baseUrl, team.id, teamData);
|
||||
});
|
||||
};
|
||||
|
||||
function generateRandomTeam(type, prefix) {
|
||||
const randomId = getRandomId();
|
||||
|
||||
@@ -88,7 +212,13 @@ function generateRandomTeam(type, prefix) {
|
||||
export const Team = {
|
||||
apiAddUserToTeam,
|
||||
apiCreateTeam,
|
||||
apiGetTeamMembersForUser,
|
||||
apiDeleteTeam,
|
||||
apiDeleteTeams,
|
||||
apiDeleteUserFromTeam,
|
||||
apiGetTeams,
|
||||
apiGetTeamsForUser,
|
||||
apiPatchTeam,
|
||||
apiPatchTeams,
|
||||
};
|
||||
|
||||
export default Team;
|
||||
|
||||
@@ -19,16 +19,130 @@ import {getResponseFromError} from './common';
|
||||
// - return value defined by `@return`
|
||||
// ****************************************************************
|
||||
|
||||
/**
|
||||
* Login to Mattermost server as sysadmin.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @return {Object} returns {user, status} on success or {error, status} on error
|
||||
*/
|
||||
export const apiAdminLogin = (baseUrl) => {
|
||||
return apiLogin(baseUrl, {
|
||||
username: testConfig.adminUsername,
|
||||
password: testConfig.adminPassword,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a user.
|
||||
* See https://api.mattermost.com/#operation/CreateUser
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} option.prefix - prefix to email and username
|
||||
* @param {Object} option.user - user object to be created
|
||||
* @return {Object} returns {user} on success or {error, status} on error
|
||||
*/
|
||||
export const apiCreateUser = async (baseUrl, {prefix = 'user', user = null} = {}) => {
|
||||
try {
|
||||
const newUser = user || generateRandomUser({prefix});
|
||||
|
||||
const response = await client.post(
|
||||
`${baseUrl}/api/v4/users`,
|
||||
newUser,
|
||||
);
|
||||
|
||||
return {user: {...response.data, password: newUser.password}};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Deactivate a user account.
|
||||
* See https://api.mattermost.com/#operation/DeleteUser
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - the user ID
|
||||
* @return {Object} returns {status} on success or {error, status} on error
|
||||
*/
|
||||
export const apiDeactivateUser = async (baseUrl, userId) => {
|
||||
try {
|
||||
const response = await client.delete(`${baseUrl}/api/v4/users/${userId}`);
|
||||
|
||||
return {status: response.status};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Demote a user to a guest.
|
||||
* See https://api.mattermost.com/#operation/DemoteUserToGuest
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - the user ID
|
||||
* @return {Object} returns {status} on success or {error, status} on error
|
||||
*/
|
||||
export const apiDemoteUserToGuest = async (baseUrl, userId) => {
|
||||
try {
|
||||
const response = await client.post(`${baseUrl}/api/v4/users/${userId}/demote`);
|
||||
|
||||
return {status: response.status};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get user from a current session.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @return {Object} returns {user} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetMe = (baseUrl) => {
|
||||
return apiGetUserById(baseUrl, 'me');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a user by ID.
|
||||
* See https://api.mattermost.com/#operation/GetUser
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - the user ID
|
||||
* @return {Object} returns {user} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetUserById = async (baseUrl, userId) => {
|
||||
try {
|
||||
const response = await client.get(`${baseUrl}/api/v4/users/${userId}`);
|
||||
|
||||
return {user: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a user by username.
|
||||
* See https://api.mattermost.com/#operation/GetUserByUsername
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} username - the username
|
||||
* @return {Object} returns {user} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetUserByUsername = async (baseUrl, username) => {
|
||||
try {
|
||||
const response = await client.get(`${baseUrl}/api/v4/users/username/${username}`);
|
||||
|
||||
return {user: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Login to Mattermost server.
|
||||
* See https://api.mattermost.com/#tag/users/paths/~1users~1login/post
|
||||
* See https://api.mattermost.com/#operation/Login
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} user.username - username of a user
|
||||
* @param {string} user.password - password of a user
|
||||
* @return {Object} returns {user, status} on success or {error, status} on error
|
||||
*/
|
||||
export const apiLogin = async (user) => {
|
||||
export const apiLogin = async (baseUrl, user) => {
|
||||
try {
|
||||
const response = await client.post(
|
||||
'/api/v4/users/login',
|
||||
`${baseUrl}/api/v4/users/login`,
|
||||
{login_id: user.username, password: user.password},
|
||||
);
|
||||
|
||||
@@ -47,64 +161,45 @@ export const apiLogin = async (user) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Login to Mattermost server as sysadmin.
|
||||
*/
|
||||
export const apiAdminLogin = async () => {
|
||||
return apiLogin({
|
||||
username: testConfig.adminUsername,
|
||||
password: testConfig.adminPassword,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Logout from the Mattermost server.
|
||||
* See https://api.mattermost.com/#tag/users/paths/~1users~1logout/post
|
||||
* See https://api.mattermost.com/#operation/Logout
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @return {Object} returns {status} on success
|
||||
*/
|
||||
export const apiLogout = async () => {
|
||||
const response = await client.post('/api/v4/users/logout');
|
||||
export const apiLogout = async (baseUrl) => {
|
||||
const response = await client.post(`${baseUrl}/api/v4/users/logout`);
|
||||
|
||||
client.defaults.headers.Cookie = '';
|
||||
|
||||
return response.data;
|
||||
return {status: response.status};
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a user.
|
||||
* See https://api.mattermost.com/#tag/users/paths/~1users/post
|
||||
* @param {Object} user - user object to be created
|
||||
* Patch user from a current session.
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {Object} userData - data to partially update a user
|
||||
* @return {Object} returns {user} on success or {error, status} on error
|
||||
*/
|
||||
export const apiCreateUser = async ({prefix = 'user', user = null} = {}) => {
|
||||
try {
|
||||
const newUser = user || generateRandomUser(prefix);
|
||||
export const apiPatchMe = (baseUrl, userData) => {
|
||||
return apiPatchUser(baseUrl, 'me', userData);
|
||||
};
|
||||
|
||||
const response = await client.post(
|
||||
'/api/v4/users',
|
||||
newUser,
|
||||
/**
|
||||
* Patch a user.
|
||||
* See https://api.mattermost.com/#operation/PatchUser
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - the user ID
|
||||
* @param {Object} userData - data to partially update a user
|
||||
* @return {Object} returns {user} on success or {error, status} on error
|
||||
*/
|
||||
export const apiPatchUser = async (baseUrl, userId, userData) => {
|
||||
try {
|
||||
const response = await client.put(
|
||||
`${baseUrl}/api/v4/users/${userId}/patch`,
|
||||
userData,
|
||||
);
|
||||
|
||||
return {user: {...response.data, password: newUser.password}};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get user from a current session.
|
||||
*/
|
||||
export const apiGetMe = () => {
|
||||
return apiGetUserById('me');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a user by ID.
|
||||
* See https://api.mattermost.com/#tag/users/paths/~1users~1{user_id}/get
|
||||
* @param {string} userId
|
||||
*/
|
||||
export const apiGetUserById = async (userId) => {
|
||||
try {
|
||||
const response = await client.get(`/api/v4/users/${userId}`);
|
||||
|
||||
return {user: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
@@ -112,41 +207,54 @@ export const apiGetUserById = async (userId) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a user by username.
|
||||
* See https://api.mattermost.com/#tag/users/paths/~1users~1username~1{username}/get
|
||||
* @param {string} username
|
||||
* Update user active status.
|
||||
* See https://api.mattermost.com/#operation/UpdateUserActive
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {string} userId - the user ID
|
||||
* @param {boolean} active - use true to set the user active, false for inactive
|
||||
* @return {Object} returns {status} on success or {error, status} on error
|
||||
*/
|
||||
export const apiGetUserByUsername = async (username) => {
|
||||
export const apiUpdateUserActiveStatus = async (baseUrl, userId, active) => {
|
||||
try {
|
||||
const response = await client.get(`/api/v4/users/username/${username}`);
|
||||
const response = await client.put(
|
||||
`${baseUrl}/api/v4/users/${userId}/active`,
|
||||
{active},
|
||||
);
|
||||
|
||||
return {user: response.data};
|
||||
return {status: response.status};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
function generateRandomUser(prefix) {
|
||||
const randomId = getRandomId();
|
||||
export const generateRandomUser = ({prefix = 'user', randomIdLength = 6} = {}) => {
|
||||
const randomId = getRandomId(randomIdLength);
|
||||
|
||||
return {
|
||||
email: `${prefix}${randomId}@sample.mattermost.com`,
|
||||
username: `${prefix}${randomId}`,
|
||||
password: 'passwd',
|
||||
first_name: `First${randomId}`,
|
||||
last_name: `Last${randomId}`,
|
||||
nickname: `Nickname${randomId}`,
|
||||
first_name: `F${randomId}`,
|
||||
last_name: `L${randomId}`,
|
||||
nickname: `N${randomId}`,
|
||||
position: `P${randomId}`,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const User = {
|
||||
apiAdminLogin,
|
||||
apiLogin,
|
||||
apiLogout,
|
||||
apiCreateUser,
|
||||
apiDeactivateUser,
|
||||
apiDemoteUserToGuest,
|
||||
apiGetMe,
|
||||
apiGetUserById,
|
||||
apiGetUserByUsername,
|
||||
apiLogin,
|
||||
apiLogout,
|
||||
apiPatchMe,
|
||||
apiPatchUser,
|
||||
apiUpdateUserActiveStatus,
|
||||
generateRandomUser,
|
||||
};
|
||||
|
||||
export default User;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
module.exports = {
|
||||
serverUrl: process.env.SITE_URL || (process.env.IOS ? 'http://localhost:8065' : 'http://10.0.2.2:8065'),
|
||||
siteUrl: process.env.SITE_URL || 'http://localhost:8065',
|
||||
smtpUrl: process.env.SMTP_URL || 'http://localhost:9001',
|
||||
adminEmail: process.env.ADMIN_EMAIL || 'sysadmin@sample.mattermost.com',
|
||||
adminUsername: process.env.ADMIN_USERNAME || 'sysadmin',
|
||||
adminPassword: process.env.ADMIN_PASSWORD || 'Sys@dmin-sample1',
|
||||
ldapServer: process.env.LDAP_SERVER || 'localhost',
|
||||
|
||||
8
detox/e2e/support/ui/component/index.js
Normal file
8
detox/e2e/support/ui/component/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// Imports here
|
||||
|
||||
// Exports here
|
||||
export {
|
||||
};
|
||||
8
detox/e2e/support/ui/screen/index.js
Normal file
8
detox/e2e/support/ui/screen/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// Imports here
|
||||
|
||||
// Exports here
|
||||
export {
|
||||
};
|
||||
238
detox/e2e/support/utils/email.js
Normal file
238
detox/e2e/support/utils/email.js
Normal file
@@ -0,0 +1,238 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import testConfig from '@support/test_config';
|
||||
import axios from 'axios';
|
||||
import jestExpect from 'expect';
|
||||
|
||||
/**
|
||||
* Get email url.
|
||||
* @returns {string} email url
|
||||
*/
|
||||
export const getEmailUrl = () => {
|
||||
const smtpUrl = testConfig.smtpUrl || 'http://localhost:9001';
|
||||
|
||||
return `${smtpUrl}/api/v1/mailbox`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get email reset email template.
|
||||
* @param {string} userEmail - the destination user email
|
||||
* @returns {string} email template
|
||||
*/
|
||||
export const getEmailResetEmailTemplate = (userEmail) => {
|
||||
return [
|
||||
'----------------------',
|
||||
'You updated your email',
|
||||
'----------------------',
|
||||
'',
|
||||
`Your email address for Mattermost has been changed to ${userEmail}.`,
|
||||
'If you did not make this change, please contact the system administrator.',
|
||||
'',
|
||||
'To change your notification preferences, log in to your team site and go to Settings > Notifications.',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get join email template.
|
||||
* @param {string} sender - the email sender
|
||||
* @param {string} userEmail - the destination user email
|
||||
* @param {Object} team - the team to join
|
||||
* @param {boolean} isGuest - true if guest; otherwise false
|
||||
* @returns {string} email template
|
||||
*/
|
||||
export const getJoinEmailTemplate = (sender, userEmail, team, isGuest = false) => {
|
||||
const baseUrl = testConfig.siteUrl;
|
||||
|
||||
return [
|
||||
`${sender} invited you to join the ${team.display_name} team.`,
|
||||
`${isGuest ? 'You were invited as a guest to collaborate with the team' : 'Start collaborating with your team on Mattermost'}`,
|
||||
'',
|
||||
`<join-link-check> Join now ( ${baseUrl}/signup_user_complete/?d=${encodeURIComponent(JSON.stringify({display_name: team.display_name.replace(' ', '+'), email: userEmail, name: team.name}))}&t=<actual-token> )`,
|
||||
'',
|
||||
'What is Mattermost?',
|
||||
'Mattermost is a flexible, open source messaging platform that enables secure team collaboration.',
|
||||
'Learn more ( mattermost.com )',
|
||||
'',
|
||||
'© 2021 Mattermost, Inc. 530 Lytton Avenue, Second floor, Palo Alto, CA, 94301',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get mention email template.
|
||||
* @param {string} sender - the email sender
|
||||
* @param {string} message - the email message
|
||||
* @param {string} postId - the post id where user is mentioned
|
||||
* @param {string} siteName - the site name
|
||||
* @param {string} teamName - the team name where user is mentioned
|
||||
* @param {string} channelDisplayName - the channel display name where user is mentioned
|
||||
* @@returns {string} email template
|
||||
*/
|
||||
export const getMentionEmailTemplate = (sender, message, postId, siteName, teamName, channelDisplayName) => {
|
||||
const baseUrl = testConfig.siteUrl;
|
||||
|
||||
return [
|
||||
`@${sender} mentioned you in a message`,
|
||||
`While you were away, @${sender} mentioned you in the ${channelDisplayName} channel.`,
|
||||
'',
|
||||
`View Message ( ${baseUrl}/landing#/${teamName}/pl/${postId} )`,
|
||||
'',
|
||||
`@${sender}`,
|
||||
'<skip-local-time-check>',
|
||||
channelDisplayName,
|
||||
'',
|
||||
message,
|
||||
'',
|
||||
'Want to change your notifications settings?',
|
||||
`Login to ${siteName} ( ${baseUrl} ) and go to Settings > Notifications`,
|
||||
'',
|
||||
'© 2021 Mattermost, Inc. 530 Lytton Avenue, Second floor, Palo Alto, CA, 94301',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get password reset email template.
|
||||
* @returns {string} email template
|
||||
*/
|
||||
export const getPasswordResetEmailTemplate = () => {
|
||||
const baseUrl = testConfig.siteUrl;
|
||||
|
||||
return [
|
||||
'Reset Your Password',
|
||||
'Click the button below to reset your password. If you didn’t request this, you can safely ignore this email.',
|
||||
'',
|
||||
`<reset-password-link-check> Reset Password ( http://${baseUrl}/reset_password_complete?token=<actual-token> )`,
|
||||
'',
|
||||
'The password reset link expires in 24 hours.',
|
||||
'',
|
||||
'© 2021 Mattermost, Inc. 530 Lytton Avenue, Second floor, Palo Alto, CA, 94301',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get email verify email template.
|
||||
* @param {string} userEmail - the destination user email
|
||||
* @returns {string} email template
|
||||
*/
|
||||
export const getEmailVerifyEmailTemplate = (userEmail) => {
|
||||
const baseUrl = testConfig.siteUrl;
|
||||
|
||||
return [
|
||||
'Verify your email address',
|
||||
`Thanks for joining ${baseUrl.split('/')[2]}. ( ${baseUrl} )`,
|
||||
'Click below to verify your email address.',
|
||||
'',
|
||||
`<email-verify-link-check> Verify Email ( ${baseUrl}/do_verify_email?token=<actual-token>&email=${encodeURIComponent(userEmail)} )`,
|
||||
'',
|
||||
'This email address was used to create an account with Mattermost.',
|
||||
'If it was not you, you can safely ignore this email.',
|
||||
'',
|
||||
'© 2021 Mattermost, Inc. 530 Lytton Avenue, Second floor, Palo Alto, CA, 94301',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get welcome email template.
|
||||
* @param {string} userEmail - the destination user email
|
||||
* @param {string} siteName - the site name
|
||||
* @param {string} teamName - the team name where user is welcome
|
||||
* @returns {string} email template
|
||||
*/
|
||||
export const getWelcomeEmailTemplate = (userEmail, siteName, teamName) => {
|
||||
const baseUrl = testConfig.siteUrl;
|
||||
|
||||
return [
|
||||
'Welcome to the team',
|
||||
`Thanks for joining ${baseUrl.split('/')[2]}. ( ${baseUrl} )`,
|
||||
'Click below to verify your email address.',
|
||||
'',
|
||||
`<email-verify-link-check> Verify Email ( ${baseUrl}/do_verify_email?token=<actual-token>&email=${encodeURIComponent(userEmail)}&redirect_to=/${teamName} )`,
|
||||
'',
|
||||
`This email address was used to create an account with ${siteName}.`,
|
||||
'If it was not you, you can safely ignore this email.',
|
||||
'',
|
||||
'Download the desktop and mobile apps',
|
||||
'For the best experience, download the apps for PC, Mac, iOS and Android.',
|
||||
'',
|
||||
'Download ( https://mattermost.com/download/#mattermostApps )',
|
||||
'',
|
||||
'© 2021 Mattermost, Inc. 530 Lytton Avenue, Second floor, Palo Alto, CA, 94301',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify email body.
|
||||
* @param {string} expectedBody - expected email body
|
||||
* @param {*} actualBody - actual email body
|
||||
*/
|
||||
export const verifyEmailBody = (expectedBody, actualBody) => {
|
||||
jestExpect(expectedBody.length).toEqual(actualBody.length);
|
||||
|
||||
for (let i = 0; i < expectedBody.length; i++) {
|
||||
if (expectedBody[i].includes('skip-local-time-check')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (expectedBody[i].includes('email-verify-link-check')) {
|
||||
jestExpect(actualBody[i]).toContain('Verify Email');
|
||||
jestExpect(actualBody[i]).toContain('do_verify_email?token=');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (expectedBody[i].includes('join-link-check')) {
|
||||
jestExpect(actualBody[i]).toContain('Join now');
|
||||
jestExpect(actualBody[i]).toContain('signup_user_complete/?d=');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (expectedBody[i].includes('reset-password-link-check')) {
|
||||
jestExpect(actualBody[i]).toContain('Reset Password');
|
||||
jestExpect(actualBody[i]).toContain('reset_password_complete?token=');
|
||||
continue;
|
||||
}
|
||||
|
||||
jestExpect(expectedBody[i]).toEqual(actualBody[i]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get recent email.
|
||||
* @param {string} username - username of email recipient
|
||||
* @param {string} mailUrl - url of email
|
||||
*/
|
||||
export const getRecentEmail = async (username, mailUrl = getEmailUrl()) => {
|
||||
const mailboxUrl = `${mailUrl}/${username}`;
|
||||
let response;
|
||||
let recentEmail;
|
||||
|
||||
try {
|
||||
response = await axios({url: mailboxUrl, method: 'get'});
|
||||
recentEmail = response.data[response.data.length - 1];
|
||||
} catch (error) {
|
||||
return {status: error.status, data: null};
|
||||
}
|
||||
|
||||
if (!recentEmail || !recentEmail.id) {
|
||||
return {status: 501, data: null};
|
||||
}
|
||||
|
||||
let recentEmailMessage;
|
||||
const mailMessageUrl = `${mailboxUrl}/${recentEmail.id}`;
|
||||
try {
|
||||
response = await axios({url: mailMessageUrl, method: 'get'});
|
||||
recentEmailMessage = response.data;
|
||||
} catch (error) {
|
||||
return {status: error.status, data: null};
|
||||
}
|
||||
|
||||
return {status: response.status, data: recentEmailMessage};
|
||||
};
|
||||
|
||||
/**
|
||||
* Split email body text.
|
||||
* @param {string} text
|
||||
* @return {string} split text
|
||||
*/
|
||||
export const splitEmailBodyText = (text) => {
|
||||
return text.split('\n').map((d) => d.trim());
|
||||
};
|
||||
@@ -1,25 +1,38 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import testConfig from '@support/test_config';
|
||||
import {v4 as uuidv4} from 'uuid';
|
||||
|
||||
export * from './email';
|
||||
|
||||
/**
|
||||
* Explicit `wait` should not normally used but made available for special cases.
|
||||
* @param {number} ms - duration in millisecond
|
||||
* @return {Promise} promise with timeout
|
||||
*/
|
||||
export const wait = async (ms) => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if android.
|
||||
* @return {boolean} true if android
|
||||
*/
|
||||
export const isAndroid = () => {
|
||||
return device.getPlatform() === 'android';
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if ios.
|
||||
* @return {boolean} true if ios
|
||||
*/
|
||||
export const isIos = () => {
|
||||
return device.getPlatform() === 'ios';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get random id.
|
||||
* @param {number} length - length on random string to return, e.g. 6 (default)
|
||||
* @return {string} random string
|
||||
*/
|
||||
@@ -30,6 +43,7 @@ export const getRandomId = (length = 6) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Capitalize first character of text.
|
||||
* @param {string} text
|
||||
* @return {string} capitalized text
|
||||
*/
|
||||
@@ -37,6 +51,17 @@ export const capitalize = (text) => {
|
||||
return text.charAt(0).toUpperCase() + text.slice(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get admin account.
|
||||
*/
|
||||
export const getAdminAccount = () => {
|
||||
return {
|
||||
username: testConfig.adminUsername,
|
||||
password: testConfig.adminPassword,
|
||||
email: testConfig.adminEmail,
|
||||
};
|
||||
};
|
||||
|
||||
const SECOND = 1000;
|
||||
const MINUTE = 60 * 1000;
|
||||
|
||||
270
detox/e2e/utils/webhook_utils.js
Normal file
270
detox/e2e/utils/webhook_utils.js
Normal file
@@ -0,0 +1,270 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
function getFullDialog(triggerId, webhookBaseUrl) {
|
||||
return {
|
||||
trigger_id: triggerId,
|
||||
url: `${webhookBaseUrl}/dialog_submit`,
|
||||
dialog: {
|
||||
callback_id: 'somecallbackid',
|
||||
title: 'Title for Full Dialog Test',
|
||||
icon_url:
|
||||
'http://www.mattermost.org/wp-content/uploads/2016/04/icon.png',
|
||||
elements: [
|
||||
{
|
||||
display_name: 'Display Name',
|
||||
name: 'realname',
|
||||
type: 'text',
|
||||
subtype: '',
|
||||
default: 'default text',
|
||||
placeholder: 'placeholder',
|
||||
help_text:
|
||||
'This a regular input in an interactive dialog triggered by a test integration.',
|
||||
optional: false,
|
||||
min_length: 0,
|
||||
max_length: 0,
|
||||
data_source: '',
|
||||
options: null,
|
||||
},
|
||||
{
|
||||
display_name: 'Email',
|
||||
name: 'someemail',
|
||||
type: 'text',
|
||||
subtype: 'email',
|
||||
default: '',
|
||||
placeholder: 'placeholder@bladekick.com',
|
||||
help_text:
|
||||
'This a regular email input in an interactive dialog triggered by a test integration.',
|
||||
optional: false,
|
||||
min_length: 0,
|
||||
max_length: 0,
|
||||
data_source: '',
|
||||
options: null,
|
||||
},
|
||||
{
|
||||
display_name: 'Number',
|
||||
name: 'somenumber',
|
||||
type: 'text',
|
||||
subtype: 'number',
|
||||
default: '',
|
||||
placeholder: '',
|
||||
help_text: '',
|
||||
optional: false,
|
||||
min_length: 0,
|
||||
max_length: 0,
|
||||
data_source: '',
|
||||
options: null,
|
||||
},
|
||||
{
|
||||
display_name: 'Password',
|
||||
name: 'somepassword',
|
||||
type: 'text',
|
||||
subtype: 'password',
|
||||
default: 'p@ssW0rd',
|
||||
placeholder: 'placeholder',
|
||||
help_text:
|
||||
'This a password input in an interactive dialog triggered by a test integration.',
|
||||
optional: true,
|
||||
min_length: 0,
|
||||
max_length: 0,
|
||||
data_source: '',
|
||||
options: null,
|
||||
},
|
||||
{
|
||||
display_name: 'Display Name Long Text Area',
|
||||
name: 'realnametextarea',
|
||||
type: 'textarea',
|
||||
subtype: '',
|
||||
default: '',
|
||||
placeholder: 'placeholder',
|
||||
help_text: '',
|
||||
optional: true,
|
||||
min_length: 5,
|
||||
max_length: 100,
|
||||
data_source: '',
|
||||
options: null,
|
||||
},
|
||||
{
|
||||
display_name: 'User Selector',
|
||||
name: 'someuserselector',
|
||||
type: 'select',
|
||||
subtype: '',
|
||||
default: '',
|
||||
placeholder: 'Select a user...',
|
||||
help_text: '',
|
||||
optional: false,
|
||||
min_length: 0,
|
||||
max_length: 0,
|
||||
data_source: 'users',
|
||||
options: null,
|
||||
},
|
||||
{
|
||||
display_name: 'Channel Selector',
|
||||
name: 'somechannelselector',
|
||||
type: 'select',
|
||||
subtype: '',
|
||||
default: '',
|
||||
placeholder: 'Select a channel...',
|
||||
help_text: 'Choose a channel from the list.',
|
||||
optional: true,
|
||||
min_length: 0,
|
||||
max_length: 0,
|
||||
data_source: 'channels',
|
||||
options: null,
|
||||
},
|
||||
{
|
||||
display_name: 'Option Selector',
|
||||
name: 'someoptionselector',
|
||||
type: 'select',
|
||||
subtype: '',
|
||||
default: '',
|
||||
placeholder: 'Select an option...',
|
||||
help_text: '',
|
||||
optional: false,
|
||||
min_length: 0,
|
||||
max_length: 0,
|
||||
data_source: '',
|
||||
options: [
|
||||
{
|
||||
text: 'Option1',
|
||||
value: 'opt1',
|
||||
},
|
||||
{
|
||||
text: 'Option2',
|
||||
value: 'opt2',
|
||||
},
|
||||
{
|
||||
text: 'Option3',
|
||||
value: 'opt3',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
display_name: 'Radio Option Selector',
|
||||
name: 'someradiooptions',
|
||||
type: 'radio',
|
||||
help_text: '',
|
||||
optional: false,
|
||||
options: [
|
||||
{
|
||||
text: 'Engineering',
|
||||
value: 'engineering',
|
||||
},
|
||||
{
|
||||
text: 'Sales',
|
||||
value: 'sales',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
display_name: 'Boolean Selector',
|
||||
placeholder: 'Was this modal helpful?',
|
||||
name: 'boolean_input',
|
||||
type: 'bool',
|
||||
default: 'True',
|
||||
optional: true,
|
||||
help_text: 'This is the help text',
|
||||
},
|
||||
],
|
||||
submit_label: 'Submit',
|
||||
notify_on_cancel: true,
|
||||
state: 'somestate',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getSimpleDialog(triggerId, webhookBaseUrl) {
|
||||
return {
|
||||
trigger_id: triggerId,
|
||||
url: `${webhookBaseUrl}/dialog_submit`,
|
||||
dialog: {
|
||||
callback_id: 'somecallbackid',
|
||||
title: 'Title for Dialog Test without elements',
|
||||
icon_url:
|
||||
'http://www.mattermost.org/wp-content/uploads/2016/04/icon.png',
|
||||
submit_label: 'Submit Test',
|
||||
notify_on_cancel: true,
|
||||
state: 'somestate',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getUserAndChannelDialog(triggerId, webhookBaseUrl) {
|
||||
return {
|
||||
trigger_id: triggerId,
|
||||
url: `${webhookBaseUrl}/dialog_submit`,
|
||||
dialog: {
|
||||
callback_id: 'somecallbackid',
|
||||
title: 'Title for Dialog Test with user and channel element',
|
||||
icon_url:
|
||||
'http://www.mattermost.org/wp-content/uploads/2016/04/icon.png',
|
||||
submit_label: 'Submit Test',
|
||||
notify_on_cancel: true,
|
||||
state: 'somestate',
|
||||
elements: [
|
||||
{
|
||||
display_name: 'User Selector',
|
||||
name: 'someuserselector',
|
||||
type: 'select',
|
||||
subtype: '',
|
||||
default: '',
|
||||
placeholder: 'Select a user...',
|
||||
help_text: '',
|
||||
optional: false,
|
||||
min_length: 0,
|
||||
max_length: 0,
|
||||
data_source: 'users',
|
||||
options: null,
|
||||
},
|
||||
{
|
||||
display_name: 'Channel Selector',
|
||||
name: 'somechannelselector',
|
||||
type: 'select',
|
||||
subtype: '',
|
||||
default: '',
|
||||
placeholder: 'Select a channel...',
|
||||
help_text: 'Choose a channel from the list.',
|
||||
optional: true,
|
||||
min_length: 0,
|
||||
max_length: 0,
|
||||
data_source: 'channels',
|
||||
options: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getBooleanDialog(triggerId, webhookBaseUrl) {
|
||||
return {
|
||||
trigger_id: triggerId,
|
||||
url: `${webhookBaseUrl}/dialog_submit`,
|
||||
dialog: {
|
||||
callback_id: 'somecallbackid',
|
||||
title: 'Title for Dialog Test with boolean element',
|
||||
icon_url:
|
||||
'http://www.mattermost.org/wp-content/uploads/2016/04/icon.png',
|
||||
submit_label: 'Submit Test',
|
||||
notify_on_cancel: true,
|
||||
state: 'somestate',
|
||||
elements: [
|
||||
{
|
||||
display_name: 'Boolean Selector',
|
||||
placeholder: 'Was this modal helpful?',
|
||||
name: 'boolean_input',
|
||||
type: 'bool',
|
||||
default: 'True',
|
||||
optional: true,
|
||||
help_text: 'This is the help text',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getFullDialog,
|
||||
getSimpleDialog,
|
||||
getUserAndChannelDialog,
|
||||
getBooleanDialog,
|
||||
};
|
||||
3481
detox/package-lock.json
generated
3481
detox/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,21 +5,22 @@
|
||||
"author": "Mattermost, Inc.",
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "7.16.7",
|
||||
"@babel/plugin-transform-modules-commonjs": "7.16.7",
|
||||
"@babel/plugin-transform-runtime": "7.16.7",
|
||||
"@babel/preset-env": "7.16.7",
|
||||
"axios": "0.24.0",
|
||||
"babel-jest": "27.4.5",
|
||||
"@babel/plugin-transform-modules-commonjs": "7.16.8",
|
||||
"@babel/plugin-transform-runtime": "7.17.0",
|
||||
"@babel/preset-env": "7.16.11",
|
||||
"axios": "0.26.0",
|
||||
"babel-jest": "27.5.1",
|
||||
"babel-plugin-module-resolver": "4.1.0",
|
||||
"client-oauth2": "github:larkox/js-client-oauth2#e24e2eb5dfcbbbb3a59d095e831dbe0012b0ac49",
|
||||
"client-oauth2": "4.3.3",
|
||||
"deepmerge": "4.2.2",
|
||||
"detox": "19.4.1",
|
||||
"detox": "19.4.5",
|
||||
"form-data": "4.0.0",
|
||||
"jest": "27.4.5",
|
||||
"jest-circus": "27.4.5",
|
||||
"jest-cli": "27.4.5",
|
||||
"jest-html-reporters": "3.0.3",
|
||||
"jest": "27.5.1",
|
||||
"jest-circus": "27.5.1",
|
||||
"jest-cli": "27.5.1",
|
||||
"jest-html-reporters": "3.0.5",
|
||||
"jest-junit": "13.0.0",
|
||||
"moment-timezone": "0.5.34",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
|
||||
295
detox/webhook_server.js
Normal file
295
detox/webhook_server.js
Normal file
@@ -0,0 +1,295 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
/* eslint-disable camelcase, no-console */
|
||||
|
||||
const axios = require('axios');
|
||||
const ClientOAuth2 = require('client-oauth2');
|
||||
const express = require('express');
|
||||
|
||||
const postMessageAs = require('./e2e/plugins/post_message_as');
|
||||
const webhookUtils = require('./e2e/utils/webhook_utils');
|
||||
const port = 3000;
|
||||
|
||||
const {
|
||||
SITE_URL,
|
||||
WEBHOOK_BASE_URL,
|
||||
ADMIN_USERNAME,
|
||||
ADMIN_PASSWORD,
|
||||
} = process.env; // eslint-disable-line no-process-env
|
||||
|
||||
const server = express();
|
||||
server.use(express.json());
|
||||
server.use(express.urlencoded({extended: true}));
|
||||
|
||||
process.title = process.argv[2];
|
||||
|
||||
server.get('/', ping);
|
||||
server.post('/message_menus', postMessageMenus);
|
||||
server.post('/dialog_request', onDialogRequest);
|
||||
server.post('/simple_dialog_request', onSimpleDialogRequest);
|
||||
server.post('/user_and_channel_dialog_request', onUserAndChannelDialogRequest);
|
||||
server.post('/dialog_submit', onDialogSubmit);
|
||||
server.post('/boolean_dialog_request', onBooleanDialogRequest);
|
||||
server.post('/slack_compatible_message_response', postSlackCompatibleMessageResponse);
|
||||
server.post('/send_message_to_channel', postSendMessageToChannel);
|
||||
server.post('/post_outgoing_webhook', postOutgoingWebhook);
|
||||
server.post('/send_oauth_credentials', postSendOauthCredentials);
|
||||
server.get('/start_oauth', getStartOAuth);
|
||||
server.get('/complete_oauth', getCompleteOauth);
|
||||
server.post('/postOAuthMessage', postOAuthMessage);
|
||||
|
||||
function ping(req, res) {
|
||||
const baseUrl = SITE_URL || 'http://localhost:8065';
|
||||
const webhookBaseUrl = WEBHOOK_BASE_URL || 'http://localhost:3000';
|
||||
|
||||
return res.json({
|
||||
message: 'I\'m alive!',
|
||||
baseUrl,
|
||||
webhookBaseUrl,
|
||||
});
|
||||
}
|
||||
|
||||
server.listen(port, () => console.log(`Webhook test server listening on port ${port}!`));
|
||||
|
||||
let appID;
|
||||
let appSecret;
|
||||
let client;
|
||||
let authedUser;
|
||||
function postSendOauthCredentials(req, res) {
|
||||
appID = req.body.appID.trim();
|
||||
appSecret = req.body.appSecret.trim();
|
||||
client = new ClientOAuth2({
|
||||
clientId: appID,
|
||||
clientSecret: appSecret,
|
||||
authorizationUri: getBaseUrl() + '/oauth/authorize',
|
||||
accessTokenUri: getBaseUrl() + '/oauth/access_token',
|
||||
redirectUri: getWebhookBaseUrl() + '/complete_oauth',
|
||||
});
|
||||
return res.status(200).send('OK');
|
||||
}
|
||||
|
||||
function getStartOAuth(req, res) {
|
||||
return res.redirect(client.code.getUri());
|
||||
}
|
||||
|
||||
function getCompleteOauth(req, res) {
|
||||
client.code.getToken(req.originalUrl).then((user) => {
|
||||
authedUser = user;
|
||||
return res.status(200).send('OK');
|
||||
}).catch((reason) => {
|
||||
return res.status(reason.status).send(reason);
|
||||
});
|
||||
}
|
||||
|
||||
async function postOAuthMessage(req, res) {
|
||||
const {channelId, message, rootId, createAt} = req.body;
|
||||
const apiUrl = getBaseUrl() + '/api/v4/posts';
|
||||
authedUser.sign({
|
||||
method: 'post',
|
||||
url: apiUrl,
|
||||
});
|
||||
try {
|
||||
await axios({
|
||||
url: apiUrl,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
Authorization: 'Bearer ' + authedUser.accessToken,
|
||||
},
|
||||
method: 'post',
|
||||
data: {
|
||||
channel_id: channelId,
|
||||
message,
|
||||
type: '',
|
||||
create_at: createAt,
|
||||
root_id: rootId,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
// Do nothing
|
||||
}
|
||||
return res.status(200).send('OK');
|
||||
}
|
||||
|
||||
function postSlackCompatibleMessageResponse(req, res) {
|
||||
const {spoiler, skipSlackParsing} = req.body.context;
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
return res.json({
|
||||
ephemeral_text: spoiler,
|
||||
skip_slack_parsing: skipSlackParsing,
|
||||
});
|
||||
}
|
||||
|
||||
function postMessageMenus(req, res) {
|
||||
let responseData = {};
|
||||
const {body} = req;
|
||||
if (body && body.context.action === 'do_something') {
|
||||
responseData = {
|
||||
ephemeral_text: `Ephemeral | ${body.type} ${body.data_source} option: ${body.context.selected_option}`,
|
||||
};
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
return res.json(responseData);
|
||||
}
|
||||
|
||||
async function openDialog(dialog) {
|
||||
const baseUrl = getBaseUrl();
|
||||
await axios({
|
||||
method: 'post',
|
||||
url: `${baseUrl}/api/v4/actions/dialogs/open`,
|
||||
data: dialog,
|
||||
});
|
||||
}
|
||||
|
||||
function onDialogRequest(req, res) {
|
||||
const {body} = req;
|
||||
if (body.trigger_id) {
|
||||
const webhookBaseUrl = getWebhookBaseUrl();
|
||||
const dialog = webhookUtils.getFullDialog(body.trigger_id, webhookBaseUrl);
|
||||
openDialog(dialog);
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
return res.json({text: 'Full dialog triggered via slash command!'});
|
||||
}
|
||||
|
||||
function onSimpleDialogRequest(req, res) {
|
||||
const {body} = req;
|
||||
if (body.trigger_id) {
|
||||
const webhookBaseUrl = getWebhookBaseUrl();
|
||||
const dialog = webhookUtils.getSimpleDialog(body.trigger_id, webhookBaseUrl);
|
||||
openDialog(dialog);
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
return res.json({text: 'Simple dialog triggered via slash command!'});
|
||||
}
|
||||
|
||||
function onUserAndChannelDialogRequest(req, res) {
|
||||
const {body} = req;
|
||||
if (body.trigger_id) {
|
||||
const webhookBaseUrl = getWebhookBaseUrl();
|
||||
const dialog = webhookUtils.getUserAndChannelDialog(body.trigger_id, webhookBaseUrl);
|
||||
openDialog(dialog);
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
return res.json({text: 'Simple dialog triggered via slash command!'});
|
||||
}
|
||||
|
||||
function onBooleanDialogRequest(req, res) {
|
||||
const {body} = req;
|
||||
if (body.trigger_id) {
|
||||
const webhookBaseUrl = getWebhookBaseUrl();
|
||||
const dialog = webhookUtils.getBooleanDialog(body.trigger_id, webhookBaseUrl);
|
||||
openDialog(dialog);
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
return res.json({text: 'Simple dialog triggered via slash command!'});
|
||||
}
|
||||
|
||||
function onDialogSubmit(req, res) {
|
||||
const {body} = req;
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
let message;
|
||||
if (body.cancelled) {
|
||||
message = 'Dialog cancelled';
|
||||
sendSysadminResponse(message, body.channel_id);
|
||||
} else {
|
||||
message = 'Dialog submitted';
|
||||
sendSysadminResponse(message, body.channel_id);
|
||||
}
|
||||
|
||||
return res.json({text: message});
|
||||
}
|
||||
|
||||
/**
|
||||
* @route "POST /send_message_to_channel?type={messageType}&channel_id={channelId}"
|
||||
* @query type - message type of empty string for regular message if not provided (default), "system_message", etc
|
||||
* @query channel_id - channel where to send the message
|
||||
*/
|
||||
function postSendMessageToChannel(req, res) {
|
||||
const channelId = req.query.channel_id;
|
||||
const response = {
|
||||
response_type: 'in_channel',
|
||||
text: 'Extra response 2',
|
||||
channel_id: channelId,
|
||||
extra_responses: [{
|
||||
response_type: 'in_channel',
|
||||
text: 'Hello World',
|
||||
channel_id: channelId,
|
||||
}],
|
||||
};
|
||||
|
||||
if (req.query.type) {
|
||||
response.type = req.query.type;
|
||||
}
|
||||
|
||||
res.json(response);
|
||||
}
|
||||
|
||||
function getWebhookBaseUrl() {
|
||||
return WEBHOOK_BASE_URL || 'http://localhost:3000';
|
||||
}
|
||||
|
||||
function getBaseUrl() {
|
||||
return SITE_URL || 'http://localhost:8065';
|
||||
}
|
||||
|
||||
// Convenient way to send response in a channel by using sysadmin account
|
||||
function sendSysadminResponse(message, channelId) {
|
||||
const username = ADMIN_USERNAME || 'sysadmin';
|
||||
const password = ADMIN_PASSWORD || 'Sys@dmin-sample1';
|
||||
const baseUrl = getBaseUrl();
|
||||
postMessageAs(baseUrl, {sender: {username, password}, message, channelId});
|
||||
}
|
||||
|
||||
const responseTypes = ['in_channel', 'comment'];
|
||||
|
||||
function getWebhookResponse(body, {responseType, username, iconUrl}) {
|
||||
const payload = Object.entries(body).map(([key, value]) => `- ${key}: "${value}"`).join('\n');
|
||||
|
||||
return `
|
||||
\`\`\`
|
||||
#### Outgoing Webhook Payload
|
||||
${payload}
|
||||
#### Webhook override to Mattermost instance
|
||||
- response_type: "${responseType}"
|
||||
- type: ""
|
||||
- username: "${username}"
|
||||
- icon_url: "${iconUrl}"
|
||||
\`\`\`
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @route "POST /post_outgoing_webhook?override_username={username}&override_icon_url={iconUrl}&response_type={comment}"
|
||||
* @query override_username - the user name that overrides the user name defined by the outgoing webhook
|
||||
* @query override_icon_url - the user icon url that overrides the user icon url defined by the outgoing webhook
|
||||
* @query response_type - "in_channel" (default) or "comment"
|
||||
*/
|
||||
function postOutgoingWebhook(req, res) {
|
||||
const {body, query} = req;
|
||||
if (!body) {
|
||||
res.status(404).send({error: 'Invalid data'});
|
||||
}
|
||||
|
||||
const responseType = query.response_type || responseTypes[0];
|
||||
const username = query.override_username || '';
|
||||
const iconUrl = query.override_icon_url || '';
|
||||
|
||||
const response = {
|
||||
text: getWebhookResponse(body, {responseType, username, iconUrl}),
|
||||
username,
|
||||
icon_url: iconUrl,
|
||||
type: '',
|
||||
response_type: responseType,
|
||||
};
|
||||
res.status(200).send(response);
|
||||
}
|
||||
Reference in New Issue
Block a user