diff --git a/packages/e2e-tests/.env-example b/packages/e2e-tests/.env-example new file mode 100644 index 00000000..b7a2b62f --- /dev/null +++ b/packages/e2e-tests/.env-example @@ -0,0 +1,5 @@ +POSTGRES_DB=automatisch +POSTGRES_USER=automatisch_user +POSTGRES_PASSWORD=automatisch_password +POSTGRES_PORT=5432 +POSTGRES_HOST=localhost \ No newline at end of file diff --git a/packages/e2e-tests/fixtures/accept-invitation-page.js b/packages/e2e-tests/fixtures/accept-invitation-page.js index 72bef636..d69ecfd7 100644 --- a/packages/e2e-tests/fixtures/accept-invitation-page.js +++ b/packages/e2e-tests/fixtures/accept-invitation-page.js @@ -1,3 +1,4 @@ +const { expect } = require('@playwright/test'); const { BasePage } = require('./base-page'); export class AcceptInvitation extends BasePage { @@ -14,6 +15,7 @@ export class AcceptInvitation extends BasePage { this.passwordConfirmationTextField = this.page.getByTestId('confirm-password-text-field'); this.submitButton = this.page.getByTestId('submit-button'); this.pageTitle = this.page.getByTestId('accept-invitation-form-title'); + this.formErrorMessage = this.page.getByTestId('accept-invitation-form-error'); } async open(token) { @@ -28,4 +30,17 @@ export class AcceptInvitation extends BasePage { await this.submitButton.click(); } + + async fillPasswordField(password) { + await this.passwordTextField.fill(password); + await this.passwordConfirmationTextField.fill(password); + } + + async excpectSubmitButtonToBeDisabled() { + await expect(this.submitButton).toBeDisabled(); + } + + async expectAlertToBeVisible() { + await expect(this.formErrorMessage).toBeVisible(); + } } diff --git a/packages/e2e-tests/fixtures/applications-page.js b/packages/e2e-tests/fixtures/applications-page.js index a84958e6..bad839dd 100644 --- a/packages/e2e-tests/fixtures/applications-page.js +++ b/packages/e2e-tests/fixtures/applications-page.js @@ -1,4 +1,3 @@ -const path = require('node:path'); const { ApplicationsModal } = require('./applications-modal'); const { AuthenticatedPage } = require('./authenticated-page'); diff --git a/packages/e2e-tests/fixtures/index.js b/packages/e2e-tests/fixtures/index.js index 4396f374..fc9dabcf 100644 --- a/packages/e2e-tests/fixtures/index.js +++ b/packages/e2e-tests/fixtures/index.js @@ -5,6 +5,7 @@ const { ExecutionsPage } = require('./executions-page'); const { FlowEditorPage } = require('./flow-editor-page'); const { UserInterfacePage } = require('./user-interface-page'); const { LoginPage } = require('./login-page'); +const { AcceptInvitation } = require('./accept-invitation-page'); const { adminFixtures } = require('./admin'); exports.test = test.extend({ @@ -46,6 +47,11 @@ exports.publicTest = test.extend({ await use(loginPage); }, + + acceptInvitationPage: async ({ page }, use) => { + const acceptInvitationPage = new AcceptInvitation(page); + await use(acceptInvitationPage); + } }); expect.extend({ diff --git a/packages/e2e-tests/fixtures/postgres-client-config.js b/packages/e2e-tests/fixtures/postgres-client-config.js new file mode 100644 index 00000000..076b6b8f --- /dev/null +++ b/packages/e2e-tests/fixtures/postgres-client-config.js @@ -0,0 +1,11 @@ +const { Client } = require('pg'); + +const client = new Client({ + host: process.env.POSTGRES_HOST, + user: process.env.POSTGRES_USERNAME, + port: process.env.POSTGRES_PORT, + password: process.env.POSTGRES_PASSWORD, + database: process.env.POSTGRES_DATABASE +}); + +exports.client = client; diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 33ef98a6..1e731307 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -32,7 +32,9 @@ "eslint": "^8.13.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", + "luxon": "^3.4.4", "micro": "^10.0.1", + "pg": "^8.12.0", "prettier": "^2.5.1" } } diff --git a/packages/e2e-tests/playwright.config.js b/packages/e2e-tests/playwright.config.js index 16f63eb6..315ca57a 100644 --- a/packages/e2e-tests/playwright.config.js +++ b/packages/e2e-tests/playwright.config.js @@ -1,4 +1,3 @@ -// @ts-check const { defineConfig, devices } = require('@playwright/test'); /** diff --git a/packages/e2e-tests/tests/apps/list-apps.spec.js b/packages/e2e-tests/tests/apps/list-apps.spec.js index 16530008..42e56880 100644 --- a/packages/e2e-tests/tests/apps/list-apps.spec.js +++ b/packages/e2e-tests/tests/apps/list-apps.spec.js @@ -1,4 +1,3 @@ -// @ts-check const { test, expect } = require('../../fixtures/index'); test.describe('Apps page', () => { diff --git a/packages/e2e-tests/tests/authentication/login.spec.js b/packages/e2e-tests/tests/authentication/login.spec.js index f43c0a86..55f3a2db 100644 --- a/packages/e2e-tests/tests/authentication/login.spec.js +++ b/packages/e2e-tests/tests/authentication/login.spec.js @@ -1,5 +1,4 @@ -// @ts-check -const { publicTest, test, expect } = require('../../fixtures/index'); +const { publicTest, expect } = require('../../fixtures/index'); publicTest.describe('Login page', () => { publicTest('shows login form', async ({ loginPage }) => { diff --git a/packages/e2e-tests/tests/connections/create-connection.spec.js b/packages/e2e-tests/tests/connections/create-connection.spec.js index 062d8ddd..511f3bb5 100644 --- a/packages/e2e-tests/tests/connections/create-connection.spec.js +++ b/packages/e2e-tests/tests/connections/create-connection.spec.js @@ -1,8 +1,7 @@ -// @ts-check const { test, expect } = require('../../fixtures/index'); test.describe('Connections page', () => { - test.beforeEach(async ({ page, connectionsPage }) => { + test.beforeEach(async ({ page }) => { await page.getByTestId('apps-page-drawer-link').click(); await page.goto('/app/ntfy/connections'); }); diff --git a/packages/e2e-tests/tests/executions/display-execution.spec.js b/packages/e2e-tests/tests/executions/display-execution.spec.js index 4b8d42ba..8e1b4995 100644 --- a/packages/e2e-tests/tests/executions/display-execution.spec.js +++ b/packages/e2e-tests/tests/executions/display-execution.spec.js @@ -1,4 +1,3 @@ -// @ts-check const { test, expect } = require('../../fixtures/index'); // no execution data exists in an empty account diff --git a/packages/e2e-tests/tests/executions/list-executions.spec.js b/packages/e2e-tests/tests/executions/list-executions.spec.js index b198d3c8..48527229 100644 --- a/packages/e2e-tests/tests/executions/list-executions.spec.js +++ b/packages/e2e-tests/tests/executions/list-executions.spec.js @@ -1,8 +1,7 @@ -// @ts-check const { test, expect } = require('../../fixtures/index'); test.describe('Executions page', () => { - test.beforeEach(async ({ page, executionsPage }) => { + test.beforeEach(async ({ page }) => { await page.getByTestId('executions-page-drawer-link').click(); }); diff --git a/packages/e2e-tests/tests/flow-editor/create-flow.spec.js b/packages/e2e-tests/tests/flow-editor/create-flow.spec.js index 5486a4cc..363cad57 100644 --- a/packages/e2e-tests/tests/flow-editor/create-flow.spec.js +++ b/packages/e2e-tests/tests/flow-editor/create-flow.spec.js @@ -1,4 +1,3 @@ -// @ts-check const { test, expect } = require('../../fixtures/index'); test('Ensure creating a new flow works', async ({ page }) => { diff --git a/packages/e2e-tests/tests/user-interface/user-interface-configuration.spec.js b/packages/e2e-tests/tests/user-interface/user-interface-configuration.spec.js index 89c5a9e9..eb9689d1 100644 --- a/packages/e2e-tests/tests/user-interface/user-interface-configuration.spec.js +++ b/packages/e2e-tests/tests/user-interface/user-interface-configuration.spec.js @@ -1,4 +1,3 @@ -// @ts-check const { test, expect } = require('../../fixtures/index'); test.describe('User interface page', () => { diff --git a/packages/e2e-tests/tests/user-invitation/invitation.spec.js b/packages/e2e-tests/tests/user-invitation/invitation.spec.js new file mode 100644 index 00000000..63a2f13e --- /dev/null +++ b/packages/e2e-tests/tests/user-invitation/invitation.spec.js @@ -0,0 +1,62 @@ +const { AdminCreateUserPage } = require('../../fixtures/admin/create-user-page'); +const { publicTest, expect } = require('../../fixtures/index'); +const { client } = require('../../fixtures/postgres-client-config'); +const { DateTime } = require('luxon'); + +publicTest.describe('Accept invitation page', () => { + publicTest.beforeAll(async () => { + await client.connect(); + }); + + publicTest.afterAll(async () => { + await client.end(); + }); + + publicTest('should not be able to set the password if token is empty', async ({ acceptInvitationPage }) => { + await acceptInvitationPage.open(''); + await acceptInvitationPage.excpectSubmitButtonToBeDisabled(); + await acceptInvitationPage.fillPasswordField('something'); + await acceptInvitationPage.excpectSubmitButtonToBeDisabled(); + }); + + publicTest('should not be able to set the password if token is expired', async ({ acceptInvitationPage, page }) => { + const expiredTokenDate = DateTime.now().minus({days: 3}).toISO(); + const expiredToken = (Math.random() + 1).toString(36).substring(2); + + const adminCreateUserPage = new AdminCreateUserPage(page); + adminCreateUserPage.seed(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)); + const user = adminCreateUserPage.generateUser(); + + const queryRole = { + text: 'SELECT * FROM roles WHERE name = $1', + values: ['Admin'] + }; + + try { + const queryRoleIdResult = await client.query(queryRole); + expect(queryRoleIdResult.rowCount).toEqual(1); + + const insertUser = { + text: 'INSERT INTO users (email, full_name, role_id, status, invitation_token, invitation_token_sent_at) VALUES ($1, $2, $3, $4, $5, $6)', + values: [user.email, user.fullName, queryRoleIdResult.rows[0].id, 'invited', expiredToken, expiredTokenDate], + }; + + const insertUserResult = await client.query(insertUser); + expect(insertUserResult.rowCount).toBe(1); + expect(insertUserResult.command).toBe('INSERT'); + } catch (err) { + console.error(err.message); + throw err; + } + + await acceptInvitationPage.open(expiredToken); + await acceptInvitationPage.acceptInvitation('something'); + await acceptInvitationPage.expectAlertToBeVisible(); + }); + + publicTest('should not be able to set the password if token is not in db', async ({ acceptInvitationPage }) => { + await acceptInvitationPage.open('abc'); + await acceptInvitationPage.acceptInvitation('something'); + await acceptInvitationPage.expectAlertToBeVisible(); + }); +}); diff --git a/packages/web/src/components/AcceptInvitationForm/index.jsx b/packages/web/src/components/AcceptInvitationForm/index.jsx index f967e031..df26f735 100644 --- a/packages/web/src/components/AcceptInvitationForm/index.jsx +++ b/packages/web/src/components/AcceptInvitationForm/index.jsx @@ -111,6 +111,7 @@ export default function ResetPasswordForm() { {acceptInvitation.isError && ( diff --git a/yarn.lock b/yarn.lock index 90c5211f..7904bbef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11349,6 +11349,11 @@ luxon@^3.0.1: resolved "https://registry.npmjs.org/luxon/-/luxon-3.0.4.tgz" integrity sha512-aV48rGUwP/Vydn8HT+5cdr26YYQiUZ42NM6ToMoaGKwYfWbfLeRkEu1wXWMHBZT6+KyLfcbbtVcoQFCbbPjKlw== +luxon@^3.4.4: + version "3.4.4" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af" + integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA== + lz-string@^1.4.4: version "1.4.4" resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz" @@ -12926,11 +12931,21 @@ performance-now@^2.1.0: resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +pg-cloudflare@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" + integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== + pg-connection-string@2.5.0, pg-connection-string@^2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz" integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== +pg-connection-string@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.4.tgz#f543862adfa49fa4e14bc8a8892d2a84d754246d" + integrity sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA== + pg-int8@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz" @@ -12941,11 +12956,21 @@ pg-pool@^3.4.1: resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz" integrity sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ== +pg-pool@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.2.tgz#3a592370b8ae3f02a7c8130d245bc02fa2c5f3f2" + integrity sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg== + pg-protocol@^1.5.0: version "1.5.0" resolved "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz" integrity sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ== +pg-protocol@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.1.tgz#21333e6d83b01faaebfe7a33a7ad6bfd9ed38cb3" + integrity sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg== + pg-types@^2.1.0: version "2.2.0" resolved "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz" @@ -12957,6 +12982,19 @@ pg-types@^2.1.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" +pg@^8.12.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.12.0.tgz#9341724db571022490b657908f65aee8db91df79" + integrity sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ== + dependencies: + pg-connection-string "^2.6.4" + pg-pool "^3.6.2" + pg-protocol "^1.6.1" + pg-types "^2.1.0" + pgpass "1.x" + optionalDependencies: + pg-cloudflare "^1.1.1" + pg@^8.7.1: version "8.7.1" resolved "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz"