test: add applications settings tests

This commit is contained in:
Jakub P.
2024-09-10 22:00:49 +02:00
committed by Ali BARIN
parent e146793d32
commit 09b2b7350c
14 changed files with 407 additions and 31 deletions

View File

@@ -0,0 +1,40 @@
import { expect } from '@playwright/test';
const { AuthenticatedPage } = require('../authenticated-page');
export class AdminApplicationAuthClientsPage extends AuthenticatedPage {
/**
* @param {import('@playwright/test').Page} page
*/
constructor(page) {
super(page);
this.authClientsTab = this.page.getByText('AUTH CLIENTS');
this.saveButton = this.page.getByTestId('submitButton');
this.successSnackbar = this.page.getByTestId('snackbar-save-admin-apps-settings-success');
this.createFirstAuthClientButton = this.page.getByTestId('no-results');
this.createAuthClientButton = this.page.getByTestId('create-auth-client-button');
this.submitAuthClientFormButton = this.page.getByTestId('submit-auth-client-form');
this.authClientEntry = this.page.getByTestId('auth-client');
}
async openAuthClientsTab() {
this.authClientsTab.click();
}
async openFirstAuthClientCreateForm() {
this.createFirstAuthClientButton.click();
}
async openAuthClientCreateForm() {
this.createAuthClientButton.click();
}
async submitAuthClientForm() {
this.submitAuthClientFormButton.click();
}
async authClientShouldBeVisible(authClientName) {
await expect(this.authClientEntry.filter({ hasText: authClientName })).toBeVisible();
}
}

View File

@@ -0,0 +1,59 @@
const { AuthenticatedPage } = require('../authenticated-page');
const { expect } = require('@playwright/test');
export class AdminApplicationSettingsPage extends AuthenticatedPage {
/**
* @param {import('@playwright/test').Page} page
*/
constructor(page) {
super(page);
this.allowCustomConnectionsSwitch = this.page.locator('[name="allowCustomConnection"]');
this.allowSharedConnectionsSwitch = this.page.locator('[name="shared"]');
this.disableConnectionsSwitch = this.page.locator('[name="disabled"]');
this.saveButton = this.page.getByTestId('submit-button');
this.successSnackbar = this.page.getByTestId('snackbar-save-admin-apps-settings-success');
}
async allowCustomConnections() {
await expect(this.disableConnectionsSwitch).not.toBeChecked();
await this.allowCustomConnectionsSwitch.check();
await this.saveButton.click();
}
async allowSharedConnections() {
await expect(this.disableConnectionsSwitch).not.toBeChecked();
await this.allowSharedConnectionsSwitch.check();
await this.saveButton.click();
}
async disallowConnections() {
await expect(this.disableConnectionsSwitch).not.toBeChecked();
await this.disableConnectionsSwitch.check();
await this.saveButton.click();
}
async disallowCustomConnections() {
await expect(this.disableConnectionsSwitch).toBeChecked();
await this.allowCustomConnectionsSwitch.uncheck();
await this.saveButton.click();
}
async disallowSharedConnections() {
await expect(this.disableConnectionsSwitch).toBeChecked();
await this.allowSharedConnectionsSwitch.uncheck();
await this.saveButton.click();
}
async allowConnections() {
await expect(this.disableConnectionsSwitch).toBeChecked();
await this.disableConnectionsSwitch.uncheck();
await this.saveButton.click();
}
async expectSuccessSnackbarToBeVisible() {
await expect(this.successSnackbar).toHaveCount(1);
await this.successSnackbar.click();
await expect(this.successSnackbar).toHaveCount(0);
}
}

View File

@@ -0,0 +1,32 @@
const { AuthenticatedPage } = require('../authenticated-page');
export class AdminApplicationsPage extends AuthenticatedPage {
screenshotPath = '/admin-settings/apps';
/**
* @param {import('@playwright/test').Page} page
*/
constructor(page) {
super(page);
this.searchInput = page.locator('[id="search-input"]');
this.appRow = page.getByTestId('app-row');
this.appsDrawerLink = page.getByTestId('apps-drawer-link');
this.appsLoader = page.getByTestId('apps-loader');
}
async openApplication(appName) {
await this.searchInput.fill(appName);
await this.appRow.locator(this.page.getByText(appName)).click();
}
async navigateTo() {
await this.profileMenuButton.click();
await this.adminMenuItem.click();
await this.appsDrawerLink.click();
await this.isMounted();
await this.appsLoader.waitFor({
state: 'detached',
});
}
}

View File

@@ -6,6 +6,10 @@ const { AdminRolesPage } = require('./roles-page');
const { AdminCreateRolePage } = require('./create-role-page');
const { AdminEditRolePage } = require('./edit-role-page');
const { AdminApplicationsPage } = require('./applications-page');
const { AdminApplicationSettingsPage } = require('./application-settings-page');
const { AdminApplicationAuthClientsPage } = require('./application-auth-clients-page');
export const adminFixtures = {
adminUsersPage: async ({ page }, use) => {
await use(new AdminUsersPage(page));
@@ -13,17 +17,26 @@ export const adminFixtures = {
adminCreateUserPage: async ({ page }, use) => {
await use(new AdminCreateUserPage(page));
},
adminEditUserPage: async ({page}, use) => {
adminEditUserPage: async ({ page }, use) => {
await use(new AdminEditUserPage(page));
},
adminRolesPage: async ({ page}, use) => {
adminRolesPage: async ({ page }, use) => {
await use(new AdminRolesPage(page));
},
adminEditRolePage: async ({ page}, use) => {
adminEditRolePage: async ({ page }, use) => {
await use(new AdminEditRolePage(page));
},
adminCreateRolePage: async ({ page}, use) => {
adminCreateRolePage: async ({ page }, use) => {
await use(new AdminCreateRolePage(page));
},
adminApplicationsPage: async ({ page }, use) => {
await use(new AdminApplicationsPage(page));
},
adminApplicationSettingsPage: async ({ page }, use) => {
await use(new AdminApplicationSettingsPage(page));
},
adminApplicationAuthClientsPage: async ({ page }, use) => {
await use(new AdminApplicationAuthClientsPage(page));
}
};

View File

@@ -30,6 +30,8 @@ export class FlowEditorPage extends AuthenticatedPage {
this.flowNameInput = this.page
.getByTestId('editableTypographyInput')
.locator('input');
this.flowStep = this.page.getByTestId('flow-step');
}
async createWebhookTrigger(workSynchronously) {
@@ -68,11 +70,11 @@ export class FlowEditorPage extends AuthenticatedPage {
}
async chooseAppAndEvent(appName, eventName) {
await expect(this.appAutocomplete).toHaveCount(1);
await this.appAutocomplete.click();
await this.page.getByRole('option', { name: appName }).click();
await expect(this.eventAutocomplete).toBeVisible();
await this.eventAutocomplete.click();
await expect(this.page.locator('[data-testid="ErrorIcon"]')).toHaveCount(2);
await Promise.all([
this.page.waitForResponse(resp => /(apps\/.*\/actions\/.*\/substeps)/.test(resp.url()) && resp.status() === 200),
this.page.getByRole('option', { name: eventName }).click(),

View File

@@ -1,6 +1,9 @@
const { Client } = require('pg');
const { Pool } = require('pg');
const client = new Client({
const pool = new Pool({
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
host: process.env.POSTGRES_HOST,
user: process.env.POSTGRES_USERNAME,
port: process.env.POSTGRES_PORT,
@@ -8,4 +11,4 @@ const client = new Client({
database: process.env.POSTGRES_DATABASE
});
exports.client = client;
exports.pgPool = pool;

View File

@@ -0,0 +1,231 @@
const { test, expect } = require('../../fixtures/index');
const { pgPool } = require('../../fixtures/postgres-config');
test.describe('Admin Applications', () => {
test.beforeAll(async () => {
const deleteAppAuthClients = {
text: 'DELETE FROM app_auth_clients WHERE app_key in ($1, $2, $3, $4, $5)',
values: ['carbone', 'spotify', 'deepl', 'mailchimp', 'reddit']
};
const deleteAppConfigs = {
text: 'DELETE FROM app_configs WHERE key in ($1, $2, $3, $4, $5)',
values: ['carbone', 'spotify', 'deepl', 'mailchimp', 'reddit']
};
try {
const deleteAppAuthClientsResult = await pgPool.query(deleteAppAuthClients);
expect(deleteAppAuthClientsResult.command).toBe('DELETE');
const deleteAppConfigsResult = await pgPool.query(deleteAppConfigs);
expect(deleteAppConfigsResult.command).toBe('DELETE');
} catch (err) {
console.error(err.message);
throw err;
}
});
test.beforeEach(async ({ adminApplicationsPage }) => {
await adminApplicationsPage.navigateTo();
});
test('Admin should be able to toggle Application settings', async ({
adminApplicationsPage,
adminApplicationSettingsPage,
page
}) => {
await adminApplicationsPage.openApplication('Carbone');
await expect(page.url()).toContain('/admin-settings/apps/carbone/settings');
await adminApplicationSettingsPage.allowCustomConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await adminApplicationSettingsPage.allowSharedConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await adminApplicationSettingsPage.disallowConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await page.reload();
await adminApplicationSettingsPage.disallowCustomConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await adminApplicationSettingsPage.disallowSharedConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await adminApplicationSettingsPage.allowConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
});
test('should allow only custom connections', async ({
adminApplicationsPage,
adminApplicationSettingsPage,
flowEditorPage,
page
}) => {
await adminApplicationsPage.openApplication('Spotify');
await expect(page.url()).toContain('/admin-settings/apps/spotify/settings');
await adminApplicationSettingsPage.allowCustomConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await page.goto('/');
await page.getByTestId('create-flow-button').click();
await page.waitForURL(
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
);
await expect(flowEditorPage.flowStep).toHaveCount(2);
const triggerStep = flowEditorPage.flowStep.last();
await triggerStep.click();
await flowEditorPage.chooseAppAndEvent("Spotify", "Create Playlist");
await flowEditorPage.connectionAutocomplete.click();
const newConnectionOption = page.getByRole('option').filter({ hasText: 'Add new connection' });
const newSharedConnectionOption = page.getByRole('option').filter({ hasText: 'Add new shared connection' });
await expect(newConnectionOption).toBeEnabled();
await expect(newConnectionOption).toHaveCount(1);
await expect(newSharedConnectionOption).toHaveCount(0);
});
test('should allow only shared connections', async ({
adminApplicationsPage,
adminApplicationSettingsPage,
adminApplicationAuthClientsPage,
flowEditorPage,
page
}) => {
await adminApplicationsPage.openApplication('Reddit');
await expect(page.url()).toContain('/admin-settings/apps/reddit/settings');
await adminApplicationSettingsPage.allowSharedConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await adminApplicationAuthClientsPage.openAuthClientsTab();
await adminApplicationAuthClientsPage.openFirstAuthClientCreateForm();
const authClientForm = page.getByTestId("auth-client-form");
await authClientForm.locator(page.getByTestId('switch')).check();
await authClientForm.locator(page.locator('[name="name"]')).fill('redditAuthClient');
await authClientForm.locator(page.locator('[name="clientId"]')).fill('redditClientId');
await authClientForm.locator(page.locator('[name="clientSecret"]')).fill('redditClientSecret');
await adminApplicationAuthClientsPage.submitAuthClientForm();
await adminApplicationAuthClientsPage.authClientShouldBeVisible('redditAuthClient');
await page.goto('/');
await page.getByTestId('create-flow-button').click();
await page.waitForURL(
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
);
await expect(flowEditorPage.flowStep).toHaveCount(2);
const triggerStep = flowEditorPage.flowStep.last();
await triggerStep.click();
await flowEditorPage.chooseAppAndEvent("Reddit", "Create link post");
await flowEditorPage.connectionAutocomplete.click();
const newConnectionOption = page.getByRole('option').filter({ hasText: 'Add new connection' });
const newSharedConnectionOption = page.getByRole('option').filter({ hasText: 'Add new shared connection' });
await expect(newConnectionOption).toHaveCount(0);
await expect(newSharedConnectionOption).toBeEnabled();
await expect(newSharedConnectionOption).toHaveCount(1);
});
test('should not allow any connections', async ({
adminApplicationsPage,
adminApplicationSettingsPage,
flowEditorPage,
page
}) => {
await adminApplicationsPage.openApplication('DeepL');
await expect(page.url()).toContain('/admin-settings/apps/deepl/settings');
await adminApplicationSettingsPage.disallowConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await page.goto('/');
await page.getByTestId('create-flow-button').click();
await page.waitForURL(
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
);
await expect(flowEditorPage.flowStep).toHaveCount(2);
const triggerStep = flowEditorPage.flowStep.last();
await triggerStep.click();
await flowEditorPage.chooseAppAndEvent("DeepL", "Translate text");
await flowEditorPage.connectionAutocomplete.click();
const newConnectionOption = page.getByRole('option').filter({ hasText: 'Add new connection' });
const newSharedConnectionOption = page.getByRole('option').filter({ hasText: 'Add new shared connection' });
const noConnectionsOption = page.locator('.MuiAutocomplete-noOptions').filter({ hasText: 'No options' });
await expect(noConnectionsOption).toHaveCount(1);
await expect(newConnectionOption).toHaveCount(0);
await expect(newSharedConnectionOption).toHaveCount(0);
});
test('should not allow new connections but only already created', async ({
adminApplicationsPage,
adminApplicationSettingsPage,
flowEditorPage,
page
}) => {
const queryUser = {
text: 'SELECT * FROM users WHERE email = $1',
values: [process.env.LOGIN_EMAIL]
};
try {
const queryUserResult = await pgPool.query(queryUser);
expect(queryUserResult.rowCount).toEqual(1);
const createMailchimpConnection = {
text: 'INSERT INTO connections (key, data, user_id, verified, draft) VALUES ($1, $2, $3, $4, $5)',
values: [
'mailchimp',
"U2FsdGVkX1+cAtdHwLiuRL4DaK/T1aljeeKyPMmtWK0AmAIsKhYwQiuyQCYJO3mdZ31z73hqF2Y+yj2Kn2/IIpLRqCxB2sC0rCDCZyolzOZ290YcBXSzYRzRUxhoOcZEtwYDKsy8AHygKK/tkj9uv9k6wOe1LjipNik4VmRhKjEYizzjLrJpbeU1oY+qW0GBpPYomFTeNf+MejSSmsUYyYJ8+E/4GeEfaonvsTSwMT7AId98Lck6Vy4wrfgpm7sZZ8xU15/HqXZNc8UCo2iTdw45xj/Oov9+brX4WUASFPG8aYrK8dl/EdaOvr89P8uIofbSNZ25GjJvVF5ymarrPkTZ7djjJXchzpwBY+7GTJfs3funR/vIk0Hq95jgOFFP1liZyqTXSa49ojG3hzojRQ==",
queryUserResult.rows[0].id,
'true',
'false'
],
};
const createMailchimpConnectionResult = await pgPool.query(createMailchimpConnection);
expect(createMailchimpConnectionResult.rowCount).toBe(1);
expect(createMailchimpConnectionResult.command).toBe('INSERT');
} catch (err) {
console.error(err.message);
throw err;
}
await adminApplicationsPage.openApplication('Mailchimp');
await expect(page.url()).toContain('/admin-settings/apps/mailchimp/settings');
await adminApplicationSettingsPage.disallowConnections();
await adminApplicationSettingsPage.expectSuccessSnackbarToBeVisible();
await page.goto('/');
await page.getByTestId('create-flow-button').click();
await page.waitForURL(
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
);
await expect(flowEditorPage.flowStep).toHaveCount(2);
const triggerStep = flowEditorPage.flowStep.last();
await triggerStep.click();
await flowEditorPage.chooseAppAndEvent("Mailchimp", "Create campaign");
await flowEditorPage.connectionAutocomplete.click();
await expect(page.getByRole('option').first()).toHaveText('Unnamed');
const existingConnection = page.getByRole('option').filter({ hasText: 'Unnamed' });
const newConnectionOption = page.getByRole('option').filter({ hasText: 'Add new connection' });
const newSharedConnectionOption = page.getByRole('option').filter({ hasText: 'Add new shared connection' });
const noConnectionsOption = page.locator('.MuiAutocomplete-noOptions').filter({ hasText: 'No options' });
await expect(await existingConnection.count()).toBeGreaterThan(0);
await expect(noConnectionsOption).toHaveCount(0);
await expect(newConnectionOption).toHaveCount(0);
await expect(newSharedConnectionOption).toHaveCount(0);
});
});

View File

@@ -3,10 +3,10 @@ import knex from 'knex';
import knexConfig from '../knexfile.js';
publicTest.describe('restore db', () => {
publicTest('clean db and perform migrations', async () => {
const knexClient = knex(knexConfig)
const migrator = knexClient.migrate;
await migrator.rollback({}, true);
await migrator.latest();
})
publicTest('clean db and perform migrations', async () => {
const knexClient = knex(knexConfig);
const migrator = knexClient.migrate;
await migrator.rollback({}, true);
await migrator.latest();
});
});

View File

@@ -1,5 +1,5 @@
const { publicTest, expect } = require('../../fixtures/index');
const { client } = require('../../fixtures/postgres-client-config');
const { pgPool } = require('../../fixtures/postgres-config');
const { DateTime } = require('luxon');
publicTest.describe('Accept invitation page', () => {
@@ -17,17 +17,9 @@ publicTest.describe('Accept invitation page', () => {
});
publicTest.describe('Accept invitation page - users', () => {
const expiredTokenDate = DateTime.now().minus({days: 3}).toISO();
const expiredTokenDate = DateTime.now().minus({ days: 3 }).toISO();
const token = (Math.random() + 1).toString(36).substring(2);
publicTest.beforeAll(async () => {
await client.connect();
});
publicTest.afterAll(async () => {
await client.end();
});
publicTest('should not be able to set the password if token is expired', async ({ acceptInvitationPage, adminCreateUserPage }) => {
adminCreateUserPage.seed(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER));
const user = adminCreateUserPage.generateUser();
@@ -38,7 +30,7 @@ publicTest.describe('Accept invitation page', () => {
};
try {
const queryRoleIdResult = await client.query(queryRole);
const queryRoleIdResult = await pgPool.query(queryRole);
expect(queryRoleIdResult.rowCount).toEqual(1);
const insertUser = {
@@ -46,7 +38,7 @@ publicTest.describe('Accept invitation page', () => {
values: [user.email, user.fullName, queryRoleIdResult.rows[0].id, 'invited', token, expiredTokenDate],
};
const insertUserResult = await client.query(insertUser);
const insertUserResult = await pgPool.query(insertUser);
expect(insertUserResult.rowCount).toBe(1);
expect(insertUserResult.command).toBe('INSERT');
} catch (err) {
@@ -68,7 +60,7 @@ publicTest.describe('Accept invitation page', () => {
};
try {
const queryRoleIdResult = await client.query(queryRole);
const queryRoleIdResult = await pgPool.query(queryRole);
expect(queryRoleIdResult.rowCount).toEqual(1);
const insertUser = {
@@ -76,7 +68,7 @@ publicTest.describe('Accept invitation page', () => {
values: [user.email, user.fullName, dateNow, queryRoleIdResult.rows[0].id, 'invited', token, dateNow],
};
const insertUserResult = await client.query(insertUser);
const insertUserResult = await pgPool.query(insertUser);
expect(insertUserResult.rowCount).toBe(1);
expect(insertUserResult.command).toBe('INSERT');
} catch (err) {

View File

@@ -49,6 +49,7 @@ function AdminApplicationAuthClientDialog(props) {
) : (
<DialogContentText tabIndex={-1} component="div">
<Form
data-test="auth-client-form"
onSubmit={submitHandler}
defaultValues={defaultValues}
render={({ formState: { isDirty } }) => (
@@ -67,6 +68,7 @@ function AdminApplicationAuthClientDialog(props) {
<InputCreator key={field.key} schema={field} />
))}
<LoadingButton
data-test="submit-auth-client-form"
type="submit"
variant="contained"
color="primary"

View File

@@ -43,7 +43,7 @@ function AdminApplicationAuthClients(props) {
return (
<div>
{sortedAuthClients.map((client) => (
<Card sx={{ mb: 1 }} key={client.id}>
<Card sx={{ mb: 1 }} key={client.id} data-test="auth-client">
<CardActionArea
component={Link}
to={URLS.ADMIN_APP_AUTH_CLIENT(appKey, client.id)}
@@ -70,7 +70,7 @@ function AdminApplicationAuthClients(props) {
))}
<Stack justifyContent="flex-end" direction="row">
<Link to={URLS.ADMIN_APP_AUTH_CLIENTS_CREATE(appKey)}>
<Button variant="contained" sx={{ mt: 2 }} component="div">
<Button variant="contained" sx={{ mt: 2 }} component="div" data-test="create-auth-client-button">
{formatMessage('createAuthClient.button')}
</Button>
</Link>

View File

@@ -87,6 +87,7 @@ function AdminApplicationSettings(props) {
</Stack>
<Stack>
<LoadingButton
data-test="submit-button"
type="submit"
variant="contained"
color="primary"

View File

@@ -21,7 +21,7 @@ function NoResultFound(props) {
);
return (
<Card elevation={0}>
<Card elevation={0} data-test="no-results">
<CardActionArea component={ActionAreaLink} {...props}>
<CardContent>
{!!to && <AddCircleIcon color="primary" />}

View File

@@ -42,6 +42,7 @@ function Switch(props) {
{...FormControlLabelProps}
control={
<MuiSwitch
data-test="switch"
{...switchProps}
{...field}
checked={value}