From 64882b31762c48e025c48195d64cfd865206401e Mon Sep 17 00:00:00 2001 From: Saturnino Abril Date: Tue, 13 Oct 2020 19:13:51 +0800 Subject: [PATCH] MM-T3180 Detox/E2E for LDAP login (#4863) * E2E for LDAP login * add LDAP test and sync * update per comment * add api commands to ensure LDAP user has team * clean up * clean up file check * update per comment --- .gitignore | 4 + detox/e2e/support/fixtures/ldap_users.json | 32 +++++ detox/e2e/support/server_api/common.js | 26 ++++ detox/e2e/support/server_api/index.js | 2 + detox/e2e/support/server_api/ldap.js | 68 ++++++++++ detox/e2e/support/server_api/system.js | 111 ++++++++++++++- detox/e2e/support/server_api/team.js | 17 +++ detox/e2e/support/server_api/user.js | 16 +++ detox/e2e/support/test_config.js | 2 + detox/e2e/support/ui/screen.js | 4 +- .../tap_send_button_repeatedly.e2e.js | 10 +- detox/e2e/test/on_boarding/login.e2e.js | 16 ++- detox/e2e/test/smoke_test/login_email.e2e.js | 7 +- detox/e2e/test/smoke_test/login_ldap.e2e.js | 71 ++++++++++ detox/package-lock.json | 127 +++--------------- detox/package.json | 3 +- 16 files changed, 382 insertions(+), 134 deletions(-) create mode 100644 detox/e2e/support/fixtures/ldap_users.json create mode 100644 detox/e2e/support/server_api/ldap.js create mode 100644 detox/e2e/test/smoke_test/login_ldap.e2e.js diff --git a/.gitignore b/.gitignore index 77db043b0..aae9dc69b 100644 --- a/.gitignore +++ b/.gitignore @@ -89,6 +89,10 @@ ios/sentry.properties coverage .tmp +# E2E testing +mattermost-license.txt +*.mattermost-license + # Bundle artifact *.jsbundle diff --git a/detox/e2e/support/fixtures/ldap_users.json b/detox/e2e/support/fixtures/ldap_users.json new file mode 100644 index 000000000..16e40be34 --- /dev/null +++ b/detox/e2e/support/fixtures/ldap_users.json @@ -0,0 +1,32 @@ +{ + "dev-1": { + "username": "dev.one", + "password": "Password1", + "email": "success+devone@simulator.amazonses.com", + "userType": "Admin" + }, + "dev-2": { + "username": "dev.two", + "password": "Password1", + "email": "success+testtwo@simulator.amazonses.com", + "userType": "Admin" + }, + "test-1": { + "username": "test.one", + "password": "Password1", + "email": "success+testone@simulator.amazonses.com", + "userType": "" + }, + "board-1": { + "username": "board.one", + "password": "Password1", + "email": "success+boardone@simulator.amazonses.com", + "userType": "" + }, + "board-2": { + "username": "board.two", + "password": "Password1", + "email": "success+boardtwo@simulator.amazonses.com", + "userType": "" + } +} diff --git a/detox/e2e/support/server_api/common.js b/detox/e2e/support/server_api/common.js index 8cf96f766..5cfc0aa39 100644 --- a/detox/e2e/support/server_api/common.js +++ b/detox/e2e/support/server_api/common.js @@ -1,6 +1,11 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import FormData from 'form-data'; +import fs from 'fs'; + +import client from './client'; + export const getResponseFromError = (err) => { const {response} = err; if (!response) { @@ -19,3 +24,24 @@ If testing against a server other than the default local instance, you may set t return {error: data, status}; }; + +export const apiUploadFile = async (name, absFilePath, requestOptions = {}) => { + if (!fs.existsSync(absFilePath)) { + return {error: {message: `File upload error. "${name}" file does not exist at ${absFilePath}`}}; + } + + const formData = new FormData(); + formData.append(name, fs.createReadStream(absFilePath)); + + try { + const response = await client.request({ + ...requestOptions, + data: formData, + headers: formData.getHeaders(), + }); + + return response; + } catch (err) { + return getResponseFromError(err); + } +}; diff --git a/detox/e2e/support/server_api/index.js b/detox/e2e/support/server_api/index.js index 64779a08d..25a4e4da3 100644 --- a/detox/e2e/support/server_api/index.js +++ b/detox/e2e/support/server_api/index.js @@ -2,6 +2,7 @@ // See LICENSE.txt for license information. import Channel from './channel'; +import Ldap from './ldap'; import Post from './post'; import Setup from './setup'; import System from './system'; @@ -10,6 +11,7 @@ import User from './user'; export { Channel, + Ldap, Post, Setup, System, diff --git a/detox/e2e/support/server_api/ldap.js b/detox/e2e/support/server_api/ldap.js new file mode 100644 index 000000000..37626ecd5 --- /dev/null +++ b/detox/e2e/support/server_api/ldap.js @@ -0,0 +1,68 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import jestExpect from 'expect'; + +import client from './client'; +import {getResponseFromError} from './common'; + +// **************************************************************** +// LDAP +// See https://api.mattermost.com/#tag/LDAP +// +// 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` +// **************************************************************** + +/** + * 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 + */ +export const apiLDAPSync = async () => { + try { + const response = await client.post('/api/v4/ldap/sync'); + + return response; + } catch (err) { + return getResponseFromError(err); + } +}; + +/** + * 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 + */ +export const apiLDAPTest = async () => { + try { + const response = await client.post('/api/v4/ldap/test'); + + return response.data; + } catch (err) { + return getResponseFromError(err); + } +}; + +/** + * Check that LDAP server can connect and is synchronized with Mattermost server. + */ +export const apiRequireLDAPServer = async () => { + const {error: testError} = await apiLDAPTest(); + jestExpect(testError).toBeUndefined(); + + const {error: syncError} = await apiLDAPSync(); + jestExpect(syncError).toBeUndefined(); +}; + +export const Ldap = { + apiLDAPSync, + apiLDAPTest, + apiRequireLDAPServer, +}; + +export default Ldap; diff --git a/detox/e2e/support/server_api/system.js b/detox/e2e/support/server_api/system.js index 3fe93ad12..8009b94b8 100644 --- a/detox/e2e/support/server_api/system.js +++ b/detox/e2e/support/server_api/system.js @@ -1,12 +1,14 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import merge from 'merge-deep'; +import merge from 'deepmerge'; +import jestExpect from 'expect'; +import path from 'path'; import testConfig from '@support/test_config'; import client from './client'; -import {getResponseFromError} from './common'; +import {apiUploadFile, getResponseFromError} from './common'; import defaultServerConfig from './default_config.json'; // **************************************************************** @@ -21,6 +23,8 @@ import defaultServerConfig from './default_config.json'; // - return value defined by `@return` // **************************************************************** +/* eslint-disable no-console */ + /** * Check system health. * See https://api.mattermost.com/#tag/system/paths/~1system~1ping/get @@ -57,7 +61,7 @@ export const apiGetConfig = async () => { export const apiUpdateConfig = async (newConfig = {}) => { try { const {config: currentConfig} = await apiGetConfig(); - const config = merge(currentConfig, getDefaultConfig(), newConfig); + const config = merge.all([currentConfig, getDefaultConfig(), newConfig]); const response = await client.put( '/api/v4/config', @@ -70,18 +74,117 @@ export const apiUpdateConfig = async (newConfig = {}) => { } }; -const getDefaultConfig = () => { +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 + * @return {Object} returns {license} on success or {error, status} on error + */ +export const apiGetClientLicense = async () => { + try { + const response = await client.get('/api/v4/license/client?format=old'); + + return {license: response.data}; + } catch (err) { + return getResponseFromError(err); + } }; +/** + * Require server license to successfully continue. + * @return {Object} returns {license} on success or fail when no license + */ +export const apiRequireLicense = async () => { + const {license} = await getClientLicense(); + + if (license.IsLicensed !== 'true') { + console.error('Server has no Enterprise license.'); + } + jestExpect(license.IsLicensed).toEqual('true'); + + return {license}; +}; + +/** + * Require server license with specific feature to successfully continue. + * @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(); + + if (license.IsLicensed !== 'true') { + console.error('Server has no Enterprise license.'); + } + jestExpect(license.IsLicensed).toEqual('true'); + + let hasLicenseKey = false; + for (const [k, v] of Object.entries(license)) { + if (k === key && v === 'true') { + hasLicenseKey = true; + break; + } + } + + if (!hasLicenseKey) { + console.error(`Server has no license for "${key}" feature.`); + } + jestExpect(hasLicenseKey).toEqual(true); + + return {license}; +}; + +/** + * Upload server license with file expected at "/detox/e2e/support/fixtures/mattermost-license.txt" + */ +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'}); + + return response; +}; + +/** + * Get client license. + * If no license, try to upload if license file is available at "/support/fixtures/mattermost-license.txt". + */ +async function getClientLicense() { + const {license} = await apiGetClientLicense(); + if (license.IsLicensed === 'true') { + return {license}; + } + + // Upload a license if server is currently not loaded with license + const response = await apiUploadLicense(); + if (response.error) { + console.warn(response.error.message); + return {license}; + } + + // Get an updated client license + const out = await apiGetClientLicense(); + return {license: out.license}; +} + export const System = { apiCheckSystemHealth, apiGetConfig, apiUpdateConfig, + apiGetClientLicense, + apiRequireLicense, + apiRequireLicenseForFeature, + apiUploadLicense, }; export default System; diff --git a/detox/e2e/support/server_api/team.js b/detox/e2e/support/server_api/team.js index ea360ef3e..ad34124fe 100644 --- a/detox/e2e/support/server_api/team.js +++ b/detox/e2e/support/server_api/team.js @@ -59,6 +59,22 @@ 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 + * @return {Object} returns {teams} on success or {error, status} on error + */ +export const apiGetTeamMembersForUser = async (userId = 'me') => { + try { + const response = await client.get(`/api/v4/users/${userId}/teams`); + + return {teams: response.data}; + } catch (err) { + return getResponseFromError(err); + } +}; + function generateRandomTeam(type, prefix) { const randomId = getRandomId(); @@ -72,6 +88,7 @@ function generateRandomTeam(type, prefix) { export const Team = { apiAddUserToTeam, apiCreateTeam, + apiGetTeamMembersForUser, }; export default Team; diff --git a/detox/e2e/support/server_api/user.js b/detox/e2e/support/server_api/user.js index 57da64f6b..5b0bd522b 100644 --- a/detox/e2e/support/server_api/user.js +++ b/detox/e2e/support/server_api/user.js @@ -111,6 +111,21 @@ 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 + */ +export const apiGetUserByUsername = async (username) => { + try { + const response = await client.get(`/api/v4/users/username/${username}`); + + return {user: response.data}; + } catch (err) { + return getResponseFromError(err); + } +}; + function generateRandomUser(prefix) { const randomId = getRandomId(); @@ -131,6 +146,7 @@ export const User = { apiCreateUser, apiGetMe, apiGetUserById, + apiGetUserByUsername, }; export default User; diff --git a/detox/e2e/support/test_config.js b/detox/e2e/support/test_config.js index 097dcbf79..22bdbdc75 100644 --- a/detox/e2e/support/test_config.js +++ b/detox/e2e/support/test_config.js @@ -6,4 +6,6 @@ module.exports = { siteUrl: process.env.SITE_URL || 'http://localhost:8065', adminUsername: process.env.ADMIN_USERNAME || 'sysadmin', adminPassword: process.env.ADMIN_PASSWORD || 'Sys@dmin-sample1', + ldapServer: process.env.LDAP_SERVER || 'localhost', + ldapPort: process.env.LDAP_PORT || 389, }; diff --git a/detox/e2e/support/ui/screen.js b/detox/e2e/support/ui/screen.js index 75c0facc2..105a53349 100644 --- a/detox/e2e/support/ui/screen.js +++ b/detox/e2e/support/ui/screen.js @@ -36,12 +36,12 @@ export async function fulfillLoginScreen(user = {}) { await element(by.id('username_input')).replaceText(user.username); // # Tap anywhere to hide keyboard - await element(by.text('Mattermost')).tap(); + await element(by.id('login_screen')).tap({x: 10, y: 10}); await element(by.id('password_input')).replaceText(user.password); // # Tap anywhere to hide keyboard - await element(by.text('Mattermost')).tap(); + await element(by.id('login_screen')).tap({x: 10, y: 10}); await element(by.id('signin_button')).tap(); } diff --git a/detox/e2e/test/messaging/tap_send_button_repeatedly.e2e.js b/detox/e2e/test/messaging/tap_send_button_repeatedly.e2e.js index 837475745..254382cb9 100644 --- a/detox/e2e/test/messaging/tap_send_button_repeatedly.e2e.js +++ b/detox/e2e/test/messaging/tap_send_button_repeatedly.e2e.js @@ -7,7 +7,7 @@ // - Use element testID when selecting an element. Create one if none. // ******************************************************************* -import nodeExpect from 'expect'; +import jestExpect from 'expect'; import {toChannelScreen} from '@support/ui/screen'; import {Channel, Post, Setup} from '@support/server_api'; @@ -48,9 +48,9 @@ describe('Messaging', () => { // # Check that no duplicate message is saved. const {channel} = await Channel.apiGetChannelByName(team.name, 'town-square'); const {posts} = await Post.apiGetPostsInChannel(channel.id); - nodeExpect(posts.length).toEqual(3); - nodeExpect(posts[0].message).toEqual(message); - nodeExpect(posts[1].message).toEqual(`${user.username} joined the team.`); - nodeExpect(posts[2].message).toEqual('sysadmin joined the team.'); + jestExpect(posts.length).toEqual(3); + jestExpect(posts[0].message).toEqual(message); + jestExpect(posts[1].message).toEqual(`${user.username} joined the team.`); + jestExpect(posts[2].message).toEqual('sysadmin joined the team.'); }); }); diff --git a/detox/e2e/test/on_boarding/login.e2e.js b/detox/e2e/test/on_boarding/login.e2e.js index dd20b5d7b..2fdcef9e9 100644 --- a/detox/e2e/test/on_boarding/login.e2e.js +++ b/detox/e2e/test/on_boarding/login.e2e.js @@ -7,15 +7,17 @@ // - Use element testID when selecting an element. Create one if none. // ******************************************************************* -import {Setup} from '@support/server_api'; +import {Setup, System} from '@support/server_api'; import {serverUrl} from '@support/test_config'; import {fulfillSelectServerScreen} from '@support/ui/screen'; import {isAndroid, timeouts, wait} from '@support/utils'; describe('On boarding', () => { + let config; let user; beforeAll(async () => { + ({config} = await System.apiGetConfig()); ({user} = await Setup.apiInit()); }); @@ -89,7 +91,7 @@ describe('On boarding', () => { await element(by.id('username_input')).typeText('any'); // Tap anywhere to hide keyboard - await element(by.text('Mattermost')).tap(); + await element(by.text(config.TeamSettings.SiteName)).tap(); // Tap "Sign in" button await element(by.id('signin_button')).tap(); @@ -103,7 +105,7 @@ describe('On boarding', () => { await element(by.id('password_input')).typeText('any'); // Tap anywhere to hide keyboard - await element(by.text('Mattermost')).tap(); + await element(by.text(config.TeamSettings.SiteName)).tap(); // Tap "Sign in" button await element(by.id('signin_button')).tap(); @@ -122,13 +124,13 @@ describe('On boarding', () => { await element(by.id('username_input')).replaceText('any'); // Tap anywhere to hide keyboard - await element(by.text('Mattermost')).tap(); + await element(by.text(config.TeamSettings.SiteName)).tap(); // Enter invalid password await element(by.id('password_input')).replaceText('any'); // Tap anywhere to hide keyboard - await element(by.text('Mattermost')).tap(); + await element(by.text(config.TeamSettings.SiteName)).tap(); // Tap "Sign in" button await element(by.id('signin_button')).tap(); @@ -147,13 +149,13 @@ describe('On boarding', () => { await element(by.id('username_input')).replaceText(user.username); // # Tap anywhere to hide keyboard - await element(by.text('Mattermost')).tap(); + await element(by.text(config.TeamSettings.SiteName)).tap(); // Enter valid password await element(by.id('password_input')).replaceText(user.password); // # Tap anywhere to hide keyboard - await element(by.text('Mattermost')).tap(); + await element(by.text(config.TeamSettings.SiteName)).tap(); // Tap "Sign in" button await element(by.id('signin_button')).tap(); diff --git a/detox/e2e/test/smoke_test/login_email.e2e.js b/detox/e2e/test/smoke_test/login_email.e2e.js index a7a177df1..87919261a 100644 --- a/detox/e2e/test/smoke_test/login_email.e2e.js +++ b/detox/e2e/test/smoke_test/login_email.e2e.js @@ -7,11 +7,12 @@ // - Use element testID when selecting an element. Create one if none. // ******************************************************************* -import {Setup} from '@support/server_api'; +import {Setup, System} from '@support/server_api'; import {serverUrl} from '@support/test_config'; describe('Smoke Tests', () => { it('MM-T3179 Log in - Email / password', async () => { + const {config} = await System.apiGetConfig(); const {user} = await Setup.apiInit(); // * Verify that it starts with the Select Server screen @@ -30,13 +31,13 @@ describe('Smoke Tests', () => { await element(by.id('username_input')).replaceText(user.username); // # Tap anywhere to hide keyboard - await element(by.text('Mattermost')).tap(); + await element(by.text(config.TeamSettings.SiteName)).tap(); // # Type in password await element(by.id('password_input')).replaceText(user.password); // # Tap anywhere to hide keyboard - await element(by.text('Mattermost')).tap(); + await element(by.text(config.TeamSettings.SiteName)).tap(); // # Tap "Sign in" button await element(by.text('Sign in')).tap(); diff --git a/detox/e2e/test/smoke_test/login_ldap.e2e.js b/detox/e2e/test/smoke_test/login_ldap.e2e.js new file mode 100644 index 000000000..85a9abd00 --- /dev/null +++ b/detox/e2e/test/smoke_test/login_ldap.e2e.js @@ -0,0 +1,71 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Ldap, Setup, System, Team, User} from '@support/server_api'; +import {serverUrl} from '@support/test_config'; +import ldapUsers from '@support/fixtures/ldap_users.json'; + +describe('Smoke Tests', () => { + const testOne = ldapUsers['test-1']; + let config; + + beforeAll(async () => { + // * Verify that the server has license with LDAP feature + await System.apiRequireLicenseForFeature('LDAP'); + + // # Enable LDAP + ({config} = await System.apiUpdateConfig({LdapSettings: {Enable: true}})); + + // * Check that LDAP server can connect and is synchronized with Mattermost server + await Ldap.apiRequireLDAPServer(); + + await ensureUserHasTeam(testOne); + }); + + it('MM-T3180 Log in - LDAP', async () => { + // * Verify that it starts with the Select Server screen + await expect(element(by.id('select_server_screen'))).toBeVisible(); + + // # Type in the server URL + await element(by.id('server_url_input')).replaceText(serverUrl); + + // # Tap connect button + await element(by.text('Connect')).tap(); + + // # Verify that it goes into Login screen + await expect(element(by.id('login_screen'))).toBeVisible(); + + // # Type in username + await element(by.id('username_input')).replaceText(testOne.username); + + // # Tap anywhere to hide keyboard + await element(by.text(config.TeamSettings.SiteName)).tap(); + + // # Type in password + await element(by.id('password_input')).replaceText(testOne.password); + + // # Tap anywhere to hide keyboard + await element(by.text(config.TeamSettings.SiteName)).tap(); + + // # Tap "Sign in" button + await element(by.text('Sign in')).tap(); + + // * Verify that the user has successfully logged in by checking it redirects into the Channel screen + await expect(element(by.id('channel_screen'))).toBeVisible(); + }); +}); + +async function ensureUserHasTeam(ldapUser) { + // # Login as LDAP user to sync with the server + await User.apiLogin(ldapUser); + + // # Login as sysadmin and ensure LDAP user is member of at least one team + await User.apiAdminLogin(); + const {user} = await User.apiGetUserByUsername(ldapUser.username); + const {teams} = await Team.apiGetTeamMembersForUser(user.id); + + if (!teams?.length) { + const {team} = await Setup.apiInit(); + await Team.apiAddUserToTeam(user.id, team.id); + } +} diff --git a/detox/package-lock.json b/detox/package-lock.json index 74716186c..00c2ee964 100644 --- a/detox/package-lock.json +++ b/detox/package-lock.json @@ -2319,30 +2319,6 @@ "wrap-ansi": "^5.1.0" } }, - "clone-deep": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", - "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", - "dev": true, - "requires": { - "for-own": "^0.1.3", - "is-plain-object": "^2.0.1", - "kind-of": "^3.0.2", - "lazy-cache": "^1.0.3", - "shallow-clone": "^0.1.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -3077,15 +3053,6 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -3093,13 +3060,13 @@ "dev": true }, "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", "dev": true, "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, @@ -5330,12 +5297,6 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true - }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -5448,28 +5409,6 @@ "object-visit": "^1.0.0" } }, - "merge-deep": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.2.tgz", - "integrity": "sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "clone-deep": "^0.2.4", - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -5543,24 +5482,6 @@ } } }, - "mixin-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", - "dev": true, - "requires": { - "for-in": "^0.1.3", - "is-extendable": "^0.1.1" - }, - "dependencies": { - "for-in": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", - "dev": true - } - } - }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -6289,6 +6210,17 @@ "uuid": "^3.3.2" }, "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -6653,35 +6585,6 @@ } } }, - "shallow-clone": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", - "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", - "dev": true, - "requires": { - "is-extendable": "^0.1.1", - "kind-of": "^2.0.1", - "lazy-cache": "^0.2.3", - "mixin-object": "^2.0.1" - }, - "dependencies": { - "kind-of": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", - "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", - "dev": true, - "requires": { - "is-buffer": "^1.0.2" - } - }, - "lazy-cache": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", - "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", - "dev": true - } - } - }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", diff --git a/detox/package.json b/detox/package.json index e3e1f1615..5ba1aa783 100644 --- a/detox/package.json +++ b/detox/package.json @@ -10,11 +10,12 @@ "axios": "0.20.0", "babel-jest": "26.3.0", "babel-plugin-module-resolver": "4.0.0", + "deepmerge": "4.2.2", "detox": "17.4.4", + "form-data": "3.0.0", "jest": "26.4.2", "jest-circus": "26.4.2", "jest-cli": "26.4.2", - "merge-deep": "3.0.2", "uuid": "8.3.0" }, "scripts": {