From 01fb6b041eddb71284ccd5246657faf8c22e97be Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Fri, 25 Feb 2022 17:05:59 +0000 Subject: [PATCH 1/9] WIP --- .../tests/connection-form.test.ts | 219 ++++++++++++++++++ .../authentication-tab/authentication-aws.tsx | 3 + .../authentication-plain.tsx | 2 + .../general-tab/direct-connection-input.tsx | 1 + 4 files changed, 225 insertions(+) create mode 100644 packages/compass-e2e-tests/tests/connection-form.test.ts diff --git a/packages/compass-e2e-tests/tests/connection-form.test.ts b/packages/compass-e2e-tests/tests/connection-form.test.ts new file mode 100644 index 00000000000..96c9b9fea73 --- /dev/null +++ b/packages/compass-e2e-tests/tests/connection-form.test.ts @@ -0,0 +1,219 @@ +import { expect } from 'chai'; +import type { Element } from 'webdriverio'; +import type { CompassBrowser } from '../helpers/compass-browser'; +import { beforeTests, afterTests, afterTest } from '../helpers/compass'; +import type { Compass } from '../helpers/compass'; +import * as Selectors from '../helpers/selectors'; + +async function getCheckedRadioValue(browser: CompassBrowser, selector: string): Promise { + const elements = await browser.$$(selector); + for (const element of elements) { + if (await element.isSelected()) { + return element.getValue(); + } + } + + return null; +} + +async function getCheckboxValue(browser: CompassBrowser, selector: string): Promise { + const element = await browser.$(selector); + if (!await element.isExisting()) { + return null; // as opposed to true for checked and false for not + } + + return element.isSelected(); +} + +async function getValue(browser: CompassBrowser, selector: string): Promise { + const element = await browser.$(selector); + if (!await element.isExisting()) { + return null; + } + + return element.getValue(); +} + +async function getMultipleValues(browser: CompassBrowser, selector: string): Promise { + const elements = await browser.$$(selector); + return Promise.all(elements.map((element) => { + return element.getValue(); + })); +} + +async function maybeExpandAdvancedOptions(browser: CompassBrowser): Promise { + const advancedButton = await browser.$('[data-testid="advanced-connection-options"'); + await advancedButton.waitForDisplayed(); + + if (await advancedButton.getAttribute('aria-expanded') === 'false') { + await advancedButton.click(); + return false; // it was collapsed and had to expand + } + + return true; // it was expanded already +} + +interface NamedPromises { + [key: string]: Promise +} + +async function promiseMap(map: NamedPromises) { + const results = await Promise.all(Object.values(map)); + return Object.fromEntries(Object.keys(map).map((k, i) => [k, results[i]])); +} + +async function browseToTab(browser: CompassBrowser, tabName: string): Promise { + // get the active tab + // if it is not the target tab, click the target tab and wait for it to become visible + // return the initially active tab so we can return to it if we want to + + const initialTab = await browser.$('[aria-label="Advanced Options Tabs"] [aria-selected="true"]').getAttribute('name'); + if (initialTab !== tabName) { + await browser.clickVisible(`[aria-label="Advanced Options Tabs"] button[name="${tabName}"]`); + } + + await browser.$(`[role="tabpanel"][aria-label="${tabName}"]`).waitForDisplayed(); + + return initialTab; +} + +async function getFormState(browser: CompassBrowser) { + const wasExpanded = await maybeExpandAdvancedOptions(browser); + + const connectionString = await browser.$('[data-testid="connectionString"]').getValue(); + + // General + const initialTab = await browseToTab(browser, 'General'); + + const defaultState = await promiseMap({ + scheme: getCheckedRadioValue(browser, 'label[for="connection-scheme-mongodb-radiobox"] input[type="radio"]'), + hosts: getMultipleValues(browser, '[aria-labelledby="connection-host-input-label"]'), + directConnection: getCheckboxValue(browser, '[data-testid="direct-connection"]') + }); + + // Authentication + await browseToTab(browser, 'Authentication'); + const authenticationState = await promiseMap({ + authMethod: getCheckedRadioValue(browser, '#authentication-method-radio-box-group input[type="radio"]'), + + // username/password + defaultUsername: getValue(browser, '[data-testid="connection-username-input"]'), + defaultPassword: getValue(browser, '[data-testid="connection-password-input"]'), + defaultAuthSource: getValue(browser, '#authSourceInput'), + defaultAuthMechanism: getCheckedRadioValue(browser, '#authentication-mechanism-radio-box-group input[type="radio"]'), + + // Kerberos + kerberosPrincipal: getValue(browser, '[data-testid="gssapi-principal-input"]'), + kerberosServiceName: getValue(browser, '[data-testid="gssapi-service-name-input"]'), + kerberosCanonicalizeHostname: getCheckedRadioValue(browser, '#canonicalize-hostname-select'), + kerberosServiceRealm: getValue(browser, '[data-testid="gssapi-service-realm-input"]'), + kerberosProvidePassword: getCheckboxValue(browser, '[data-testid="gssapi-password-checkbox"]'), + kerberosPassword: getValue(browser, '[data-testid="gssapi-password-input"]'), + + // LDAP + ldapUsername: getValue(browser, '[data-testid="connection-plain-username-input"]'), + ldapPassword: getValue(browser, '[data-testid="connection-plain-password-input"]'), + + // AWS IAM + awsAccessKeyId: getValue(browser, '[data-testid="connection-form-aws-access-key-id-input"]'), + awsSecretAccessKey: getValue(browser, '[data-testid="connection-form-aws-secret-access-key-input"]'), + awsSessionToken: getValue(browser, '[data-testid="connection-form-aws-secret-token-input"]') + }); + + // TLS/SSL + await browseToTab(browser, 'TLS/SSL'); + const tlsState = {}; + + // Proxy/SSH Tunnel + await browseToTab(browser, 'Proxy/SSH Tunnel'); + const proxyState = {}; + + // Advanced + await browseToTab(browser, 'Advanced'); + const advancedState = {}; + + const result = { + connectionString, + ...defaultState, + ...authenticationState, + ...tlsState, + ...proxyState, + ...advancedState + } + + // restore the initial state + if (wasExpanded) { + // get back to the tab it was on + await browseToTab(browser, initialTab); + } else { + // collapse it again + await browser.clickVisible('[data-testid="advanced-connection-options"]'); + } + + return result; +} + +describe.only('Connection form', function () { + let compass: Compass; + let browser: CompassBrowser; + + before(async function () { + compass = await beforeTests(); + browser = compass.browser; + }); + + after(async function () { + await afterTests(compass, this.currentTest); + }); + + beforeEach(async function () { + await browser.clickVisible(Selectors.SidebarNewConnectionButton); + }); + + afterEach(async function () { + await afterTest(compass, this.currentTest); + }); + + it('starts with the expected initial state', async function () { + const state = await getFormState(browser); + expect(state).to.deep.equal({ + connectionString: 'mongodb://localhost:27017', + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'AUTH_NONE', + username: null, + password: null, + authSource: null, + authMechanism: null, + + }); + }); + + it('prompts when re-enabling Edit Connection String'); + + it('builds a URI for mongodb scheme'); + it('builds a URI for mongodb+srv scheme'); + it('builds a URI for username/password authentication'); + it('builds a URI for X.509 authentication'); + it('builds a URI for Kerberos authentication'); + it('builds a URI for LDAP authentication'); + it('builds a URI for AWS IAM authentication'); + it('builds a URI for optional TLS/SSL authentication'); + it('builds a URI for Socks5 authentication'); + it('builds a URI with advanced options') + + it('parses a URI for mongodb scheme'); + it('parses a URI for mongodb+srv scheme'); + it('parses a URI for username/password authentication'); + it('parses a URI for X.509 authentication'); + it('parses a URI for Kerberos authentication'); + it('parses a URI for LDAP authentication'); + it('parses a URI for AWS IAM authentication'); + it('parses a URI for optional TLS/SSL authentication'); + it('parses a URI for Socks5 authentication'); + it('parses a URI with advanced options'); + + it('does not update the URI for SSH tunnel with password authentication'); + it('does not update the URI for SSH tunnel with identity file authentication'); +}); \ No newline at end of file diff --git a/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-aws.tsx b/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-aws.tsx index 995a85aae23..e77c1ec6917 100644 --- a/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-aws.tsx +++ b/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-aws.tsx @@ -33,6 +33,7 @@ function AuthenticationAWS({ <> ) => { @@ -48,6 +49,7 @@ function AuthenticationAWS({ ) => { @@ -65,6 +67,7 @@ function AuthenticationAWS({ ) => { diff --git a/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-plain.tsx b/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-plain.tsx index 453722e17ea..afa6ea0a98d 100644 --- a/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-plain.tsx +++ b/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-plain.tsx @@ -32,6 +32,7 @@ function AuthenticationPlain({ <> ) => { @@ -48,6 +49,7 @@ function AuthenticationPlain({ ) => { diff --git a/packages/connection-form/src/components/advanced-options-tabs/general-tab/direct-connection-input.tsx b/packages/connection-form/src/components/advanced-options-tabs/general-tab/direct-connection-input.tsx index f2fdc9cae38..a61840e1673 100644 --- a/packages/connection-form/src/components/advanced-options-tabs/general-tab/direct-connection-input.tsx +++ b/packages/connection-form/src/components/advanced-options-tabs/general-tab/direct-connection-input.tsx @@ -46,6 +46,7 @@ function DirectConnectionInput({ return ( <> Date: Mon, 28 Feb 2022 14:03:24 +0000 Subject: [PATCH 2/9] extract connection form state and check initial form state --- .../tests/connection-form.test.ts | 669 +++++++++++++++--- 1 file changed, 559 insertions(+), 110 deletions(-) diff --git a/packages/compass-e2e-tests/tests/connection-form.test.ts b/packages/compass-e2e-tests/tests/connection-form.test.ts index 96c9b9fea73..b4c429623af 100644 --- a/packages/compass-e2e-tests/tests/connection-form.test.ts +++ b/packages/compass-e2e-tests/tests/connection-form.test.ts @@ -1,11 +1,280 @@ import { expect } from 'chai'; -import type { Element } from 'webdriverio'; import type { CompassBrowser } from '../helpers/compass-browser'; import { beforeTests, afterTests, afterTest } from '../helpers/compass'; import type { Compass } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; -async function getCheckedRadioValue(browser: CompassBrowser, selector: string): Promise { +describe('Connection form', function () { + let compass: Compass; + let browser: CompassBrowser; + + before(async function () { + compass = await beforeTests(); + browser = compass.browser; + }); + + after(async function () { + await afterTests(compass, this.currentTest); + }); + + beforeEach(async function () { + await resetForm(browser); + }); + + afterEach(async function () { + await afterTest(compass, this.currentTest); + }); + + it('starts with the expected initial state', async function () { + const state = await getFormState(browser); + expect(state).to.deep.equal({ + connectionString: 'mongodb://localhost:27017', + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'AUTH_NONE', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + }); + }); + + it('prompts when re-enabling Edit Connection String'); + + it('parses a URI for multiple hosts', async function () { + await browser.setValueVisible( + Selectors.ConnectionStringInput, + 'mongodb://localhost:27017,localhost:27018/' + ); + + const state = await getFormState(browser); + expect(state).to.deep.equal({ + connectionString: 'mongodb://localhost:27017,localhost:27018/', + scheme: 'MONGODB', + hosts: ['localhost:27017', 'localhost:27018'], + authMethod: 'AUTH_NONE', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + }); + }); + + it('parses a URI for mongodb+srv scheme', async function () { + await browser.setValueVisible( + Selectors.ConnectionStringInput, + 'mongodb+srv://localhost' + ); + + const state = await getFormState(browser); + expect(state).to.deep.equal({ + connectionString: 'mongodb+srv://localhost', + scheme: 'MONGODB_SRV', + hosts: ['localhost'], + authMethod: 'AUTH_NONE', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + }); + }); + + it('parses a URI for username/password authentication', async function () { + await browser.setValueVisible( + Selectors.ConnectionStringInput, + 'mongodb://foo:bar@localhost:27017/?authSource=source&authMechanism=SCRAM-SHA-1' + ); + + const state = await getFormState(browser); + expect(state).to.deep.equal({ + connectionString: + 'mongodb://foo:bar@localhost:27017/?authSource=source&authMechanism=SCRAM-SHA-1', + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'DEFAULT', + defaultUsername: 'foo', + defaultPassword: 'bar', + defaultAuthSource: 'source', + defaultAuthMechanism: 'SCRAM-SHA-1', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + }); + }); + + it('parses a URI for X.509 authentication', async function () { + await browser.setValueVisible( + Selectors.ConnectionStringInput, + 'mongodb://localhost:27017/?authMechanism=MONGODB-X509&tls=true&tlsCAFile=foo.pem&tlsCertificateKeyFile=bar.pem&tlsInsecure=true&tlsAllowInvalidHostnames=true&tlsAllowInvalidCertificates=true&tlsCertificateKeyFilePassword=password' + ); + + const state = await getFormState(browser); + expect(state).to.deep.equal({ + connectionString: + 'mongodb://localhost:27017/?authMechanism=MONGODB-X509&tls=true&tlsCAFile=foo.pem&tlsCertificateKeyFile=bar.pem&tlsInsecure=true&tlsAllowInvalidHostnames=true&tlsAllowInvalidCertificates=true&tlsCertificateKeyFilePassword=password', + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'MONGODB-X509', + proxyMethod: 'none', + sslConnection: 'ON', + tlsCAFile: 'foo.pem', + tlsCertificateKeyFile: 'bar.pem', + clientKeyPassword: 'password', + tlsInsecure: true, + tlsAllowInvalidHostnames: true, + tlsAllowInvalidCertificates: true, + }); + }); + + it('parses a URI for Kerberos authentication', async function () { + await browser.setValueVisible( + Selectors.ConnectionStringInput, + 'mongodb://principal:password@localhost:27017/?authMechanism=GSSAPI&authSource=%24external&authMechanismProperties=SERVICE_NAME%3Aservice+name%2CCANONICALIZE_HOST_NAME%3Aforward%2CSERVICE_REALM%3Aservice+realm' + ); + + const state = await getFormState(browser); + expect(state).to.deep.equal({ + connectionString: + 'mongodb://principal:password@localhost:27017/?authMechanism=GSSAPI&authSource=%24external&authMechanismProperties=SERVICE_NAME%3Aservice+name%2CCANONICALIZE_HOST_NAME%3Aforward%2CSERVICE_REALM%3Aservice+realm', + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'GSSAPI', + kerberosPassword: 'password', + kerberosPrincipal: 'principal', + kerberosProvidePassword: true, + kerberosServiceName: 'service name', + kerberosCanonicalizeHostname: 'forward', + kerberosServiceRealm: 'service realm', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + }); + }); + + it('parses a URI for LDAP authentication', async function () { + await browser.setValueVisible( + Selectors.ConnectionStringInput, + 'mongodb://username:password@localhost:27017/?authMechanism=PLAIN&authSource=%24external' + ); + + const state = await getFormState(browser); + expect(state).to.deep.equal({ + connectionString: + 'mongodb://username:password@localhost:27017/?authMechanism=PLAIN&authSource=%24external', + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'PLAIN', + ldapUsername: 'username', + ldapPassword: 'password', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + }); + }); + + it('parses a URI for AWS IAM authentication', async function () { + await browser.setValueVisible( + Selectors.ConnectionStringInput, + 'mongodb://id:key@localhost:27017/?authMechanism=MONGODB-AWS&authSource=%24external&authMechanismProperties=AWS_SESSION_TOKEN%3Atoken' + ); + + const state = await getFormState(browser); + expect(state).to.deep.equal({ + connectionString: + 'mongodb://id:key@localhost:27017/?authMechanism=MONGODB-AWS&authSource=%24external&authMechanismProperties=AWS_SESSION_TOKEN%3Atoken', + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'MONGODB-AWS', + awsAccessKeyId: 'id', + awsSecretAccessKey: 'key', + awsSessionToken: 'token', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + }); + }); + + it('parses a URI for Socks5 authentication', async function () { + await browser.setValueVisible( + Selectors.ConnectionStringInput, + 'mongodb://localhost:27017/?proxyHost=hostname&proxyPort=1234&proxyUsername=username&proxyPassword=password' + ); + + const state = await getFormState(browser); + expect(state).to.deep.equal({ + connectionString: + 'mongodb://localhost:27017/?proxyHost=hostname&proxyPort=1234&proxyUsername=username&proxyPassword=password', + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'AUTH_NONE', + proxyMethod: 'socks', + socksHost: 'hostname', + socksPort: '1234', + socksUsername: 'username', + socksPassword: 'password', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + }); + }); + + it('parses a URI with advanced options', async function () { + await browser.setValueVisible( + Selectors.ConnectionStringInput, + 'mongodb://localhost:27017/default-db?readPreference=primary&replicaSet=replica-set&connectTimeoutMS=1234' + ); + + const state = await getFormState(browser); + expect(state).to.deep.equal({ + connectionString: + 'mongodb://localhost:27017/default-db?readPreference=primary&replicaSet=replica-set&connectTimeoutMS=1234', + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: false, + authMethod: 'AUTH_NONE', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + readPreference: 'primary', + replicaSet: 'replica-set', + defaultDatabase: 'default-db', + urlOptions: { + connectTimeoutMS: '1234', + }, + }); + }); + + it('does not update the URI for SSH tunnel with password authentication'); + it( + 'does not update the URI for SSH tunnel with identity file authentication' + ); +}); + +async function getCheckedRadioValue( + browser: CompassBrowser, + selector: string +): Promise { const elements = await browser.$$(selector); for (const element of elements) { if (await element.isSelected()) { @@ -16,37 +285,81 @@ async function getCheckedRadioValue(browser: CompassBrowser, selector: string): return null; } -async function getCheckboxValue(browser: CompassBrowser, selector: string): Promise { +async function getCheckboxValue( + browser: CompassBrowser, + selector: string +): Promise { const element = await browser.$(selector); - if (!await element.isExisting()) { + if (!(await element.isExisting())) { return null; // as opposed to true for checked and false for not } return element.isSelected(); } -async function getValue(browser: CompassBrowser, selector: string): Promise { +async function getText( + browser: CompassBrowser, + selector: string +): Promise { const element = await browser.$(selector); - if (!await element.isExisting()) { + if (!(await element.isExisting())) { return null; } - return element.getValue(); + const text = await element.getText(); + return text || null; +} + +async function getFilename( + browser: CompassBrowser, + selector: string +): Promise { + const text = await getText(browser, selector); + return text === 'Select a file...' ? null : text; } -async function getMultipleValues(browser: CompassBrowser, selector: string): Promise { +async function getValue( + browser: CompassBrowser, + selector: string +): Promise { + const element = await browser.$(selector); + if (!(await element.isExisting())) { + return null; + } + + const value = await element.getValue(); + return value || null; +} + +async function getMultipleValues( + browser: CompassBrowser, + selector: string +): Promise { const elements = await browser.$$(selector); - return Promise.all(elements.map((element) => { - return element.getValue(); - })); + const results = ( + await Promise.all( + elements.map((element) => { + return element.getValue(); + }) + ) + ).filter((result) => result !== ''); + + return results.length ? results : null; } -async function maybeExpandAdvancedOptions(browser: CompassBrowser): Promise { - const advancedButton = await browser.$('[data-testid="advanced-connection-options"'); +async function maybeExpandAdvancedOptions( + browser: CompassBrowser +): Promise { + const advancedButton = await browser.$( + '[data-testid="advanced-connection-options"' + ); await advancedButton.waitForDisplayed(); - if (await advancedButton.getAttribute('aria-expanded') === 'false') { + if ((await advancedButton.getAttribute('aria-expanded')) === 'false') { await advancedButton.click(); + await browser.waitUntil(async () => { + return (await advancedButton.getAttribute('aria-expanded')) === 'true'; + }); return false; // it was collapsed and had to expand } @@ -54,25 +367,41 @@ async function maybeExpandAdvancedOptions(browser: CompassBrowser): Promise + [key: string]: Promise; } async function promiseMap(map: NamedPromises) { const results = await Promise.all(Object.values(map)); - return Object.fromEntries(Object.keys(map).map((k, i) => [k, results[i]])); + return Object.fromEntries( + Object.keys(map) + .map((k, i) => [k, results[i]]) + .filter(([, v]) => v !== null) + ); } -async function browseToTab(browser: CompassBrowser, tabName: string): Promise { +async function browseToTab( + browser: CompassBrowser, + tabName: string +): Promise { // get the active tab // if it is not the target tab, click the target tab and wait for it to become visible // return the initially active tab so we can return to it if we want to - const initialTab = await browser.$('[aria-label="Advanced Options Tabs"] [aria-selected="true"]').getAttribute('name'); + const initialTab = await browser + .$('[aria-label="Advanced Options Tabs"] [aria-selected="true"]') + .getAttribute('name'); if (initialTab !== tabName) { - await browser.clickVisible(`[aria-label="Advanced Options Tabs"] button[name="${tabName}"]`); + await browser.clickVisible( + `[aria-label="Advanced Options Tabs"] button[name="${tabName}"]` + ); + await browser + .$(`[role="tabpanel"][aria-label="${initialTab}"]`) + .waitForDisplayed({ reverse: true }); } - await browser.$(`[role="tabpanel"][aria-label="${tabName}"]`).waitForDisplayed(); + await browser + .$(`[role="tabpanel"][aria-label="${tabName}"]`) + .waitForDisplayed(); return initialTab; } @@ -80,57 +409,231 @@ async function browseToTab(browser: CompassBrowser, tabName: string): Promise [ + k, + advancedState.urlOptionValues[i], + ]) + ); + + delete advancedState.urlOptionKeys; + delete advancedState.urlOptionValues; + } const result = { connectionString, @@ -138,8 +641,8 @@ async function getFormState(browser: CompassBrowser) { ...authenticationState, ...tlsState, ...proxyState, - ...advancedState - } + ...advancedState, + }; // restore the initial state if (wasExpanded) { @@ -148,72 +651,18 @@ async function getFormState(browser: CompassBrowser) { } else { // collapse it again await browser.clickVisible('[data-testid="advanced-connection-options"]'); + + await browser.waitUntil(async () => { + const advancedButton = await browser.$( + '[data-testid="advanced-connection-options"' + ); + return (await advancedButton.getAttribute('aria-expanded')) === 'false'; + }); } return result; } -describe.only('Connection form', function () { - let compass: Compass; - let browser: CompassBrowser; - - before(async function () { - compass = await beforeTests(); - browser = compass.browser; - }); - - after(async function () { - await afterTests(compass, this.currentTest); - }); - - beforeEach(async function () { - await browser.clickVisible(Selectors.SidebarNewConnectionButton); - }); - - afterEach(async function () { - await afterTest(compass, this.currentTest); - }); - - it('starts with the expected initial state', async function () { - const state = await getFormState(browser); - expect(state).to.deep.equal({ - connectionString: 'mongodb://localhost:27017', - scheme: 'MONGODB', - hosts: ['localhost:27017'], - directConnection: false, - authMethod: 'AUTH_NONE', - username: null, - password: null, - authSource: null, - authMechanism: null, - - }); - }); - - it('prompts when re-enabling Edit Connection String'); - - it('builds a URI for mongodb scheme'); - it('builds a URI for mongodb+srv scheme'); - it('builds a URI for username/password authentication'); - it('builds a URI for X.509 authentication'); - it('builds a URI for Kerberos authentication'); - it('builds a URI for LDAP authentication'); - it('builds a URI for AWS IAM authentication'); - it('builds a URI for optional TLS/SSL authentication'); - it('builds a URI for Socks5 authentication'); - it('builds a URI with advanced options') - - it('parses a URI for mongodb scheme'); - it('parses a URI for mongodb+srv scheme'); - it('parses a URI for username/password authentication'); - it('parses a URI for X.509 authentication'); - it('parses a URI for Kerberos authentication'); - it('parses a URI for LDAP authentication'); - it('parses a URI for AWS IAM authentication'); - it('parses a URI for optional TLS/SSL authentication'); - it('parses a URI for Socks5 authentication'); - it('parses a URI with advanced options'); - - it('does not update the URI for SSH tunnel with password authentication'); - it('does not update the URI for SSH tunnel with identity file authentication'); -}); \ No newline at end of file +async function resetForm(browser: CompassBrowser) { + await browser.clickVisible(Selectors.SidebarNewConnectionButton); +} From a25719c17380be8809cdb62f11d73e68fbc40e33 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Wed, 2 Mar 2022 14:13:38 +0000 Subject: [PATCH 3/9] tests for the connection form fields --- packages/compass-e2e-tests/fixtures/ca.pem | 23 + .../compass-e2e-tests/fixtures/client.pem | 47 ++ .../helpers/commands/set-value-visible.ts | 17 +- .../tests/connection-form.test.ts | 571 ++++++++++++++++-- .../advanced-tab/url-options-table.tsx | 1 + .../general-tab/host-input.tsx | 2 +- 6 files changed, 599 insertions(+), 62 deletions(-) create mode 100644 packages/compass-e2e-tests/fixtures/ca.pem create mode 100644 packages/compass-e2e-tests/fixtures/client.pem diff --git a/packages/compass-e2e-tests/fixtures/ca.pem b/packages/compass-e2e-tests/fixtures/ca.pem new file mode 100644 index 00000000000..a1102228465 --- /dev/null +++ b/packages/compass-e2e-tests/fixtures/ca.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDvzCCAqegAwIBAgIUWHODQPXBJh8z9c+OvakB5EWSGykwDQYJKoZIhvcNAQEL +BQAwbzELMAkGA1UEBhMCQVUxDDAKBgNVBAgMA05TVzEVMBMGA1UECgwMT3JnYW5p +c2F0aW9uMQwwCgYDVQQLDANDQXMxDTALBgNVBAMMBHJvb3QxHjAcBgkqhkiG9w0B +CQEWD3VzZXJAZG9tYWluLmNvbTAeFw0yMjAxMTExOTAyMDhaFw0zMjAxMDkxOTAy +MDhaMG8xCzAJBgNVBAYTAkFVMQwwCgYDVQQIDANOU1cxFTATBgNVBAoMDE9yZ2Fu +aXNhdGlvbjEMMAoGA1UECwwDQ0FzMQ0wCwYDVQQDDARyb290MR4wHAYJKoZIhvcN +AQkBFg91c2VyQGRvbWFpbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC71ot540Z/uAZDXxUUacY8nScIFOeTeUAqVi4Dct5k7+PYe1HKOCtV+Uzk +98Wjys9GIrl7IQnk9cyLfqPQzc9/fc0zGYtMeQZY3DcrGfUOEoCxNIawBIkJHrL9 +2pynlyvjFV5Ve3/inwZZuuX1HbQJMm4kVrWijjxaKJjtTYxmYDQtNdbi0aa00QCm +No4dk1/z2R2xiaMBledJvUwmVObfgshrNFhCCHb0d4X5buDNIj1J7gkxkxNMq0sH +mCgsnYqsdFeRPdf63+lQ+SEraeOieV0j2sPiZUjm7bGPotjQfGGXm6hiLM6Ut0N5 +G+4StcmQjs+OAw0PwQNnp2cMHTVpAgMBAAGjUzBRMB0GA1UdDgQWBBThU9SZ3NLh +NrXDgNOaI+HOLPPhojAfBgNVHSMEGDAWgBThU9SZ3NLhNrXDgNOaI+HOLPPhojAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQC3PXkIl1sTtgU1HcMZ +RTC8rRgeSdGX2w4fQqO98s4ZC4RPNj8hf2PGTJnkyhg+sySX2IXaNJnHKaSfMRU3 +1UCDdubNHXTf+JIlcmrGYa/MYgcXHfjyZWhrUcNq64NFXglaG7EAf0IPZf3zlmiH +5y6PU0A1VDqfufKAdX2/fVFhOvI1oy8sk0nBLXcTEncPkxKRxul0/0C+gqXb6T/b +1vZybGyPBZzDCrm5bkNfeNSvbjOiJvfAEBcqsVObma858sYxJ4ijUdbJ9a7KivtY +V5noXQzx2gtUM9vY/NFBRk10YmSocQVi8+5zxrfMKkbFYhRJvp9mXOfXm9ohYrNh +JFIx +-----END CERTIFICATE----- diff --git a/packages/compass-e2e-tests/fixtures/client.pem b/packages/compass-e2e-tests/fixtures/client.pem new file mode 100644 index 00000000000..7cb110d77be --- /dev/null +++ b/packages/compass-e2e-tests/fixtures/client.pem @@ -0,0 +1,47 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAwWV9IejXo0WIzq1I9EFUsvt/3xulyC1kyKzendGFPM/sedyT +qYpuu5/jvjpZT8AiGxhlcHkdIHgZGkhsw4TgZa1F7yIbvwXzbXp61n+2Vdrbau9/ +etQWh4JdHPNBtI76oRlpcZFVNOmxLG6KGoR+VZD5Ep12aGDblW9u6tO4svHju+eO +yU17K9z6dAhlutBYxWTzjH5CWXPci+ETnjKdzLDixuX6qXqWm/iFN//6m3ZNYVgZ +bikxUpfr9lixOZ6kbdumhkqEMB/g7Ff1kA94Gh2GZrhEcFRijQCTynVnxK8bMRPr +7INYYt+Cq/n5XUtZNK6MgOdo7+GNpdokKXdxiQIDAQABAoIBAEFrbEXBNtiDGAEZ +uvjs9JLK4nl9J7osKNB9MZzfGBv3Fb4vMEAElOqg7nAV2spQavkRapb+USz/kzZB +05Db7PBYCbUXq50VjT+2U8ElWG94ZgqpU00gWzKhlY/Kdap3Ry6hovqIt/L7LjSi +e9L2iKm3LZnva/No+D53gGStE+hrtaEd2uJqWj+uo3RaEdI1mpkfUEPvon6mA5g8 +lEPLPBwKX/U9qyVbvTS71nWd2SJ657ke7EAT4afMzBUbr5zNXPqK8y1l5aHINViu +SSIhf+TtRWiq+sVPpivAfYi+2hdNRbZj9pF/291SBukH5vX+1i8SFS0QTmjemp6/ +e8IxYYECgYEA8RoQ9ShthSoTKf3YUtdqrXVP2ScBgZ/sf8DqvBkUfewmMuyHYNP5 +fcNiddUMlfkywzr00TfGk80rCBdmR1B0z4qRN3HUdqbJkupZbgzy2gkgCRTIALSZ +WpQH4MNXPs8yWz6kY2y/1kOxeqXmIf22de7/yK8vvTiAuMtRlhghqzkCgYEAzVjI +PxGN3Gq/DOEOKARrJR7j86mnf03RYVENOTnm6pHofRIjMf9GA0IM6Abgs0f45Kv2 +sfdDotNDS/xTq9qUOQHS1or8RVyOofjaTnZAfufm1zgi9xx7ymVTuehQ1T+JyXRi +nKICVbsERbjiMrGDudQnktr/B6I2E42o0yZH6NECgYB6uwsg0PiXTei9enOxD+lE +7S+9WcbBhngsPDcBkz7ELv7u80qitqUNKPWpB8/FVDpL+WoASoUyXcFm+ApfiQw9 +TctITxCZaaO4vsTRaZQB+50sIkEBYu2hlzM/bhCub2ix2/xwhD5PQtxIk8THTtCf +zg60yMDjcKzN5OneHuCcuQKBgEXkK/PbY69b3b7ictEH//fYdbmfStU3hUZcMeYC +YSgBlWHMZJJF0myVdJMclCoxGvOp3ANip3Cp+0PHlCrv8HceucYv9AEfqaRTcOo3 +sWgAQj+Kacw1s6SFrGOgQLL0N0+L/2xUb/sB9khMzyB17uG25elEBH1ypnktRALq +CpuhAoGAYSJhJ+mKXG2xVC4mvwddFbBIKytynm2viaH1s4sOX/lNk3x9/L4+A6Wm +WibJ3UNyAJ7MmwAGgFvGHHZGW6kiGRh20bk6dQQyzRWY6TQLQELeGUkYS8qClPSq +cH0eu97Q1vnagDk/FEDnE5mSrxWIakXo1xXnYvGJlahgPZD/718= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDWTCCAkECAQMwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UEBhMCQVUxDDAKBgNV +BAgMA05TVzEVMBMGA1UECgwMT3JnYW5pc2F0aW9uMQwwCgYDVQQLDANDQXMxDTAL +BgNVBAMMBHJvb3QxHjAcBgkqhkiG9w0BCQEWD3VzZXJAZG9tYWluLmNvbTAeFw0y +MjAxMTExOTAyMDhaFw0zMjAxMDkxOTAyMDhaMHYxCzAJBgNVBAYTAkFVMQwwCgYD +VQQIDANOU1cxFTATBgNVBAoMDE9yZ2FuaXNhdGlvbjEQMA4GA1UECwwHY2xpZW50 +czEQMA4GA1UEAwwHY2xpZW50MTEeMBwGCSqGSIb3DQEJARYPdXNlckBkb21haW4u +Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwWV9IejXo0WIzq1I +9EFUsvt/3xulyC1kyKzendGFPM/sedyTqYpuu5/jvjpZT8AiGxhlcHkdIHgZGkhs +w4TgZa1F7yIbvwXzbXp61n+2Vdrbau9/etQWh4JdHPNBtI76oRlpcZFVNOmxLG6K +GoR+VZD5Ep12aGDblW9u6tO4svHju+eOyU17K9z6dAhlutBYxWTzjH5CWXPci+ET +njKdzLDixuX6qXqWm/iFN//6m3ZNYVgZbikxUpfr9lixOZ6kbdumhkqEMB/g7Ff1 +kA94Gh2GZrhEcFRijQCTynVnxK8bMRPr7INYYt+Cq/n5XUtZNK6MgOdo7+GNpdok +KXdxiQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQATnp2hYq7dYzcIlVSWji35JNHh +VJcteLNi7OVHHYADZk46a00/pKQCf0ACTQeK2xNNkpH9RNH8q1c0L4e2JuugCe/o +PEJQUmk2b39KqjUsGTkMt9GHoPUGOxP1aknUoUYTvOXzX4XcQAJgtpK7x7qiG0yR +rds48FIXSr9TQnEN/Ag65m+VrISKZ/IsHT/GpBqT+GlwS+2CXak4Omj870mIiny/ +UzoEOHywzZ7hdQj9HQiatLFFIA52N+5H+Z5beCFSzdfEUn37TI8m04jZnApJxhRU +P7ghytVx0w4RdFAy4ttU+VUPT2tsPN3o4bpFSd/IRYGgB7bh9V2WxjCw7oTF +-----END CERTIFICATE----- diff --git a/packages/compass-e2e-tests/helpers/commands/set-value-visible.ts b/packages/compass-e2e-tests/helpers/commands/set-value-visible.ts index c542fac5fbe..4a7a3846c98 100644 --- a/packages/compass-e2e-tests/helpers/commands/set-value-visible.ts +++ b/packages/compass-e2e-tests/helpers/commands/set-value-visible.ts @@ -5,7 +5,18 @@ export async function setValueVisible( selector: string, value: string ): Promise { - const element = await browser.$(selector); - await element.waitForDisplayed(); - await element.setValue(value); + await browser.waitUntil( + async () => { + const element = await browser.$(selector); + await element.waitForDisplayed(); + await element.clearValue(); + await element.setValue(value); + const actualValue = await element.getValue(); + if (actualValue !== value) { + console.log(actualValue, '!==', value); + } + return actualValue === value; + }, + { timeout: 60000 } + ); } diff --git a/packages/compass-e2e-tests/tests/connection-form.test.ts b/packages/compass-e2e-tests/tests/connection-form.test.ts index b4c429623af..e69476d9b62 100644 --- a/packages/compass-e2e-tests/tests/connection-form.test.ts +++ b/packages/compass-e2e-tests/tests/connection-form.test.ts @@ -1,3 +1,4 @@ +import path from 'path'; import { expect } from 'chai'; import type { CompassBrowser } from '../helpers/compass-browser'; import { beforeTests, afterTests, afterTest } from '../helpers/compass'; @@ -43,15 +44,45 @@ describe('Connection form', function () { it('prompts when re-enabling Edit Connection String'); - it('parses a URI for multiple hosts', async function () { + it('parses a URI for direct connection', async function () { + const connectionString = 'mongodb://localhost:27017/?directConnection=true'; + await browser.setValueVisible( Selectors.ConnectionStringInput, - 'mongodb://localhost:27017,localhost:27018/' + connectionString ); + const expectedState = { + connectionString, + scheme: 'MONGODB', + hosts: ['localhost:27017'], + directConnection: true, + authMethod: 'AUTH_NONE', + proxyMethod: 'none', + sslConnection: 'DEFAULT', + tlsAllowInvalidCertificates: false, + tlsAllowInvalidHostnames: false, + tlsInsecure: false, + }; + const state = await getFormState(browser); - expect(state).to.deep.equal({ - connectionString: 'mongodb://localhost:27017,localhost:27018/', + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$('[data-testid="connectionString"]').getValue() + ).to.equal(connectionString); + }); + + it('parses a URI for multiple hosts', async function () { + const connectionString = 'mongodb://localhost:27017,localhost:27018/'; + await browser.setValueVisible( + Selectors.ConnectionStringInput, + connectionString + ); + + const expectedState = { + connectionString, scheme: 'MONGODB', hosts: ['localhost:27017', 'localhost:27018'], authMethod: 'AUTH_NONE', @@ -60,18 +91,26 @@ describe('Connection form', function () { tlsAllowInvalidCertificates: false, tlsAllowInvalidHostnames: false, tlsInsecure: false, - }); + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$('[data-testid="connectionString"]').getValue() + ).to.equal(connectionString); }); it('parses a URI for mongodb+srv scheme', async function () { + const connectionString = 'mongodb+srv://localhost/'; await browser.setValueVisible( Selectors.ConnectionStringInput, - 'mongodb+srv://localhost' + connectionString ); - const state = await getFormState(browser); - expect(state).to.deep.equal({ - connectionString: 'mongodb+srv://localhost', + const expectedState = { + connectionString, scheme: 'MONGODB_SRV', hosts: ['localhost'], authMethod: 'AUTH_NONE', @@ -80,19 +119,28 @@ describe('Connection form', function () { tlsAllowInvalidCertificates: false, tlsAllowInvalidHostnames: false, tlsInsecure: false, - }); + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$('[data-testid="connectionString"]').getValue() + ).to.equal(connectionString); }); it('parses a URI for username/password authentication', async function () { + const connectionString = + 'mongodb://foo:bar@localhost:27017/?authMechanism=SCRAM-SHA-1&authSource=source'; + await browser.setValueVisible( Selectors.ConnectionStringInput, - 'mongodb://foo:bar@localhost:27017/?authSource=source&authMechanism=SCRAM-SHA-1' + connectionString ); - const state = await getFormState(browser); - expect(state).to.deep.equal({ - connectionString: - 'mongodb://foo:bar@localhost:27017/?authSource=source&authMechanism=SCRAM-SHA-1', + const expectedState = { + connectionString, scheme: 'MONGODB', hosts: ['localhost:27017'], directConnection: false, @@ -106,44 +154,71 @@ describe('Connection form', function () { tlsAllowInvalidCertificates: false, tlsAllowInvalidHostnames: false, tlsInsecure: false, - }); + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$('[data-testid="connectionString"]').getValue() + ).to.equal(connectionString); }); it('parses a URI for X.509 authentication', async function () { - await browser.setValueVisible( - Selectors.ConnectionStringInput, - 'mongodb://localhost:27017/?authMechanism=MONGODB-X509&tls=true&tlsCAFile=foo.pem&tlsCertificateKeyFile=bar.pem&tlsInsecure=true&tlsAllowInvalidHostnames=true&tlsAllowInvalidCertificates=true&tlsCertificateKeyFilePassword=password' - ); - - const state = await getFormState(browser); - expect(state).to.deep.equal({ - connectionString: - 'mongodb://localhost:27017/?authMechanism=MONGODB-X509&tls=true&tlsCAFile=foo.pem&tlsCertificateKeyFile=bar.pem&tlsInsecure=true&tlsAllowInvalidHostnames=true&tlsAllowInvalidCertificates=true&tlsCertificateKeyFilePassword=password', + const fixturesPath = path.resolve(__dirname, '..', 'fixtures'); + const tlsCAFile = path.join(fixturesPath, 'ca.pem'); + const tlsCertificateKeyFile = path.join(fixturesPath, 'client.pem'); + const connectionString = `mongodb://localhost:27017/?authMechanism=MONGODB-X509&authSource=%24external&tls=true&tlsCAFile=${encodeURIComponent( + tlsCAFile + )}&tlsCertificateKeyFile=${encodeURIComponent( + tlsCertificateKeyFile + )}&tlsCertificateKeyFilePassword=password&tlsInsecure=true&tlsAllowInvalidHostnames=true&tlsAllowInvalidCertificates=true`; + + const expectedState = { + connectionString, scheme: 'MONGODB', hosts: ['localhost:27017'], directConnection: false, authMethod: 'MONGODB-X509', proxyMethod: 'none', sslConnection: 'ON', - tlsCAFile: 'foo.pem', - tlsCertificateKeyFile: 'bar.pem', + tlsCAFile: 'ca.pem', + tlsCertificateKeyFile: 'client.pem', clientKeyPassword: 'password', tlsInsecure: true, tlsAllowInvalidHostnames: true, tlsAllowInvalidCertificates: true, - }); + }; + + await browser.setValueVisible( + Selectors.ConnectionStringInput, + connectionString + ); + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + expectedState.tlsCAFile = tlsCAFile; + expectedState.tlsCertificateKeyFile = tlsCertificateKeyFile; + + await setFormState(browser, expectedState); + expect( + await browser.$('[data-testid="connectionString"]').getValue() + ).to.equal(connectionString); }); it('parses a URI for Kerberos authentication', async function () { + const connectionString = + 'mongodb://principal:password@localhost:27017/?authMechanism=GSSAPI&authSource=%24external&authMechanismProperties=SERVICE_NAME%3Aservice+name%2CCANONICALIZE_HOST_NAME%3Aforward%2CSERVICE_REALM%3Aservice+realm'; + await browser.setValueVisible( Selectors.ConnectionStringInput, - 'mongodb://principal:password@localhost:27017/?authMechanism=GSSAPI&authSource=%24external&authMechanismProperties=SERVICE_NAME%3Aservice+name%2CCANONICALIZE_HOST_NAME%3Aforward%2CSERVICE_REALM%3Aservice+realm' + connectionString ); - const state = await getFormState(browser); - expect(state).to.deep.equal({ - connectionString: - 'mongodb://principal:password@localhost:27017/?authMechanism=GSSAPI&authSource=%24external&authMechanismProperties=SERVICE_NAME%3Aservice+name%2CCANONICALIZE_HOST_NAME%3Aforward%2CSERVICE_REALM%3Aservice+realm', + const expectedState = { + connectionString, scheme: 'MONGODB', hosts: ['localhost:27017'], directConnection: false, @@ -159,19 +234,27 @@ describe('Connection form', function () { tlsAllowInvalidCertificates: false, tlsAllowInvalidHostnames: false, tlsInsecure: false, - }); + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$('[data-testid="connectionString"]').getValue() + ).to.equal(connectionString); }); it('parses a URI for LDAP authentication', async function () { + const connectionString = + 'mongodb://username:password@localhost:27017/?authMechanism=PLAIN&authSource=%24external'; await browser.setValueVisible( Selectors.ConnectionStringInput, - 'mongodb://username:password@localhost:27017/?authMechanism=PLAIN&authSource=%24external' + connectionString ); - const state = await getFormState(browser); - expect(state).to.deep.equal({ - connectionString: - 'mongodb://username:password@localhost:27017/?authMechanism=PLAIN&authSource=%24external', + const expectedState = { + connectionString, scheme: 'MONGODB', hosts: ['localhost:27017'], directConnection: false, @@ -183,19 +266,28 @@ describe('Connection form', function () { tlsAllowInvalidCertificates: false, tlsAllowInvalidHostnames: false, tlsInsecure: false, - }); + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$('[data-testid="connectionString"]').getValue() + ).to.equal(connectionString); }); it('parses a URI for AWS IAM authentication', async function () { + const connectionString = + 'mongodb://id:key@localhost:27017/?authMechanism=MONGODB-AWS&authSource=%24external&authMechanismProperties=AWS_SESSION_TOKEN%3Atoken'; + await browser.setValueVisible( Selectors.ConnectionStringInput, - 'mongodb://id:key@localhost:27017/?authMechanism=MONGODB-AWS&authSource=%24external&authMechanismProperties=AWS_SESSION_TOKEN%3Atoken' + connectionString ); - const state = await getFormState(browser); - expect(state).to.deep.equal({ - connectionString: - 'mongodb://id:key@localhost:27017/?authMechanism=MONGODB-AWS&authSource=%24external&authMechanismProperties=AWS_SESSION_TOKEN%3Atoken', + const expectedState = { + connectionString, scheme: 'MONGODB', hosts: ['localhost:27017'], directConnection: false, @@ -208,19 +300,28 @@ describe('Connection form', function () { tlsAllowInvalidCertificates: false, tlsAllowInvalidHostnames: false, tlsInsecure: false, - }); + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$('[data-testid="connectionString"]').getValue() + ).to.equal(connectionString); }); it('parses a URI for Socks5 authentication', async function () { + const connectionString = + 'mongodb://localhost:27017/?proxyHost=hostname&proxyPort=1234&proxyUsername=username&proxyPassword=password'; + await browser.setValueVisible( Selectors.ConnectionStringInput, - 'mongodb://localhost:27017/?proxyHost=hostname&proxyPort=1234&proxyUsername=username&proxyPassword=password' + connectionString ); - const state = await getFormState(browser); - expect(state).to.deep.equal({ - connectionString: - 'mongodb://localhost:27017/?proxyHost=hostname&proxyPort=1234&proxyUsername=username&proxyPassword=password', + const expectedState = { + connectionString, scheme: 'MONGODB', hosts: ['localhost:27017'], directConnection: false, @@ -234,19 +335,27 @@ describe('Connection form', function () { tlsAllowInvalidCertificates: false, tlsAllowInvalidHostnames: false, tlsInsecure: false, - }); + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$('[data-testid="connectionString"]').getValue() + ).to.equal(connectionString); }); it('parses a URI with advanced options', async function () { + const connectionString = + 'mongodb://localhost:27017/default-db?readPreference=primary&replicaSet=replica-set&connectTimeoutMS=1234&maxPoolSize=100'; await browser.setValueVisible( Selectors.ConnectionStringInput, - 'mongodb://localhost:27017/default-db?readPreference=primary&replicaSet=replica-set&connectTimeoutMS=1234' + connectionString ); - const state = await getFormState(browser); - expect(state).to.deep.equal({ - connectionString: - 'mongodb://localhost:27017/default-db?readPreference=primary&replicaSet=replica-set&connectTimeoutMS=1234', + const expectedState = { + connectionString, scheme: 'MONGODB', hosts: ['localhost:27017'], directConnection: false, @@ -261,8 +370,17 @@ describe('Connection form', function () { defaultDatabase: 'default-db', urlOptions: { connectTimeoutMS: '1234', + maxPoolSize: '100', }, - }); + }; + + const state = await getFormState(browser); + expect(state).to.deep.equal(expectedState); + + await setFormState(browser, expectedState); + expect( + await browser.$('[data-testid="connectionString"]').getValue() + ).to.equal(connectionString); }); it('does not update the URI for SSH tunnel with password authentication'); @@ -541,6 +659,8 @@ async function getFormState(browser: CompassBrowser) { '#ssh-options-radio-box-group input[type="radio"]' ), + // SSH with Password + // NOTE: these don't go in the URI so will likely never return values sshPasswordHost: getValue( browser, '[data-testid="ssh-password-tab-content"] [data-testid="host"]' @@ -558,6 +678,8 @@ async function getFormState(browser: CompassBrowser) { '[data-testid="ssh-password-tab-content"] [data-testid="password"' ), + // SSH with Identity File + // NOTE: same as above these are unlikely ever return values in these tests sshIdentityHost: getValue( browser, '[data-testid="ssh-identity-tab-content"] [data-testid="host"]' @@ -665,4 +787,337 @@ async function getFormState(browser: CompassBrowser) { async function resetForm(browser: CompassBrowser) { await browser.clickVisible(Selectors.SidebarNewConnectionButton); + + await browser.waitUntil(async () => { + return ( + (await browser.$('[data-testid="connectionString"]').getValue()) === + 'mongodb://localhost:27017' + ); + }); +} + +async function clickParent(browser: CompassBrowser, selector: string) { + const element = await browser.$(selector).parentElement(); + await element.waitForExist(); + await element.click(); +} + +async function setFormState(browser: CompassBrowser, state: any) { + await resetForm(browser); + + await maybeExpandAdvancedOptions(browser); + + // General + await browseToTab(browser, 'General'); + + await clickParent( + browser, + `#connection-schema-radio-box-group input[value="${ + state.scheme as string + }"]` + ); + + for (let i = 0; i < state.hosts.length; ++i) { + if (i > 0) { + await browser.clickVisible( + '[data-testid="host-input-container"]:last-child [data-testid="connection-add-host-button"]' + ); + } + await browser.setValueVisible( + `[data-testid="connection-host-input-${i}"]`, + state.hosts[i] + ); + } + + if (state.directConnection) { + await clickParent(browser, '[data-testid="direct-connection"]'); + } + + // Authentication + await browseToTab(browser, 'Authentication'); + + await clickParent( + browser, + `#authentication-method-radio-box-group input[value="${ + state.authMethod as string + }"]` + ); + + // Username/Password + if (state.defaultUsername) { + await browser.setValueVisible( + '[data-testid="connection-username-input"]', + state.defaultUsername + ); + await browser.setValueVisible( + '[data-testid="connection-password-input"]', + state.defaultPassword + ); + } + if (state.defaultAuthSource) { + await browser.setValueVisible('#authSourceInput', state.defaultAuthSource); + } + if (state.defaultAuthMechanism) { + await clickParent( + browser, + `#authentication-mechanism-radio-box-group input[value="${ + state.defaultAuthMechanism as string + }"]` + ); + } + + // Kerberos + if (state.kerberosPrincipal) { + await browser.setValueVisible( + '[data-testid="gssapi-principal-input"]', + state.kerberosPrincipal + ); + } + if (state.kerberosServiceName) { + await browser.setValueVisible( + '[data-testid="gssapi-service-name-input"]', + state.kerberosServiceName + ); + } + if (state.kerberosCanonicalizeHostname) { + await clickParent( + browser, + `#canonicalize-hostname-select input[value="${ + state.kerberosCanonicalizeHostname as string + }"]` + ); + } + if (state.kerberosServiceRealm) { + await browser.setValueVisible( + '[data-testid="gssapi-service-realm-input"]', + state.kerberosServiceRealm + ); + } + if (state.kerberosServiceRealm) { + await clickParent(browser, '[data-testid="gssapi-password-checkbox"]'); + } + if (state.kerberosPassword) { + await browser.setValueVisible( + '[data-testid="gssapi-password-input"]', + state.kerberosPassword + ); + } + + // LDAP + if (state.ldapUsername) { + await browser.setValueVisible( + '[data-testid="connection-plain-username-input"]', + state.ldapUsername + ); + await browser.setValueVisible( + '[data-testid="connection-plain-password-input"]', + state.ldapPassword + ); + } + + // AWS IAM + if (state.awsAccessKeyId) { + await browser.setValueVisible( + '[data-testid="connection-form-aws-access-key-id-input"]', + state.awsAccessKeyId + ); + await browser.setValueVisible( + '[data-testid="connection-form-aws-secret-access-key-input"]', + state.awsSecretAccessKey + ); + } + if (state.awsSessionToken) { + await browser.setValueVisible( + '[data-testid="connection-form-aws-secret-token-input"]', + state.awsSessionToken + ); + } + + // TLS/SSL + await browseToTab(browser, 'TLS/SSL'); + + await clickParent( + browser, + `#connection-schema-radio-box-group input[value="${ + state.sslConnection as string + }"]` + ); + + if (state.tlsCAFile) { + await browser.selectFile( + '[data-testid="tlsCAFile-input"]', + state.tlsCAFile + ); + } + if (state.tlsCertificateKeyFile) { + await browser.selectFile( + '[data-testid="tlsCertificateKeyFile-input"]', + state.tlsCertificateKeyFile + ); + } + if (state.clientKeyPassword) { + await browser.setValueVisible( + '[data-testid="tlsCertificateKeyFilePassword-input"]', + state.clientKeyPassword + ); + } + if (state.tlsInsecure) { + await clickParent(browser, '[data-testid="tlsInsecure-input"]'); + } + if (state.tlsAllowInvalidHostnames) { + await clickParent( + browser, + '[data-testid="tlsAllowInvalidHostnames-input"]' + ); + } + if (state.tlsAllowInvalidCertificates) { + await clickParent( + browser, + '[data-testid="tlsAllowInvalidCertificates-input"]' + ); + } + + // Proxy/SSH Tunnel + await browseToTab(browser, 'Proxy/SSH Tunnel'); + + //proxyMethod + await clickParent( + browser, + `#ssh-options-radio-box-group input[value="${state.proxyMethod as string}"]` + ); + + // SSH with Password + // NOTE: these don't affect the URI + if (state.sshPasswordHost) { + await browser.setValueVisible( + '[data-testid="ssh-password-tab-content"] [data-testid="host"]', + state.sshPasswordHost + ); + await browser.setValueVisible( + '[data-testid="ssh-password-tab-content"] [data-testid="port"]', + state.sshPasswordPort + ); + } + if (state.sshPasswordUsername) { + await browser.setValueVisible( + '[data-testid="ssh-password-tab-content"] [data-testid="username"]', + state.sshPasswordUsername + ); + } + if (state.sshPasswordPassword) { + await browser.setValueVisible( + '[data-testid="ssh-password-tab-content"] [data-testid="password"]', + state.sshPasswordPassword + ); + } + + // SSH with Identity File + // NOTE: these don't affect the URI + if (state.sshIdentityHost) { + await browser.setValueVisible( + '[data-testid="ssh-identity-tab-content"] [data-testid="host"]', + state.sshIdentityHost + ); + await browser.setValueVisible( + '[data-testid="ssh-identity-tab-content"] [data-testid="port"]', + state.sshIdentityPort + ); + } + if (state.sshIdentityUsername) { + await browser.setValueVisible( + '[data-testid="ssh-identity-tab-content"] [data-testid="username"]', + state.sshIdentityUsername + ); + } + if (state.sshIdentityKeyFile) { + await browser.selectFile( + '[data-testid="ssh-identity-tab-content"] [data-testid="identityKeyFile"]', + state.sshIdentityKeyFile + ); + } + if (state.sshIdentityPassword) { + await browser.setValueVisible( + '[data-testid="ssh-identity-tab-content"] [data-testid="password"]', + state.sshIdentityPassword + ); + } + + // Socks5 + if (state.socksHost) { + await browser.setValueVisible( + '[data-testid="socks-tab-content"] [data-testid="proxyHost"]', + state.socksHost + ); + } + if (state.socksPort) { + await browser.setValueVisible( + '[data-testid="socks-tab-content"] [data-testid="proxyPort"]', + state.socksPort + ); + } + if (state.socksUsername) { + await browser.setValueVisible( + '[data-testid="socks-tab-content"] [data-testid="proxyUsername"]', + state.socksUsername + ); + } + if (state.socksPassword) { + await browser.setValueVisible( + '[data-testid="socks-tab-content"] [data-testid="proxyPassword"]', + state.socksPassword + ); + } + + // Advanced + await browseToTab(browser, 'Advanced'); + + if (state.readPreference) { + await clickParent( + browser, + `[data-testid="read-preferences"] input[value="${ + state.readPreference as string + }"]` + ); + } + if (state.replicaSet) { + await browser.setValueVisible( + '[data-testid="connection-advanced-tab"] [data-testid="replica-set"]', + state.replicaSet + ); + } + if (state.defaultDatabase) { + await browser.setValueVisible( + '[data-testid="connection-advanced-tab"] [data-testid="default-database"]', + state.defaultDatabase + ); + } + if (state.urlOptions) { + for (const [index, [key, value]] of Object.entries( + state.urlOptions + ).entries()) { + // key + await browser.clickVisible( + `[data-testid="url-options-table"] tr:nth-child(${ + index + 1 + }) button[name="name"]` + ); + const options = await browser.$$('#select-key-menu [role="option"]'); + for (const option of options) { + const span = await option.$(`span=${key}`); + if (await span.isExisting()) { + await span.waitForDisplayed(); + await span.click(); + break; + } + } + + // value + await browser.setValueVisible( + `[data-testid="url-options-table"] tr:nth-child(${ + index + 1 + }) input[aria-labelledby="Enter value"]`, + value as string + ); + } + } } diff --git a/packages/connection-form/src/components/advanced-options-tabs/advanced-tab/url-options-table.tsx b/packages/connection-form/src/components/advanced-options-tabs/advanced-tab/url-options-table.tsx index bbd63bc158b..f39b14b360d 100644 --- a/packages/connection-form/src/components/advanced-options-tabs/advanced-tab/url-options-table.tsx +++ b/packages/connection-form/src/components/advanced-options-tabs/advanced-tab/url-options-table.tsx @@ -156,6 +156,7 @@ function UrlOptionsTable({ >