From d333c883108d2abd611925e105f2693ce69ee844 Mon Sep 17 00:00:00 2001 From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:31:08 +0200 Subject: [PATCH 1/6] Added missing tests - Group bulk assign - Webhook add/modify/change state/delete --- e2e/config.ts | 2 + e2e/tests/auth.spec.ts | 32 +++- e2e/tests/groups.spec.ts | 42 ++++- e2e/tests/webhook.spec.ts | 152 ++++++++++++++++++ e2e/utils/controllers/groups.ts | 21 +++ e2e/utils/controllers/webhooks.ts | 35 ++++ .../GroupsManagement/GroupsManagement.tsx | 1 + .../modals/AddGroupModal/AddGroupModal.tsx | 1 + .../users/UsersOverview/UsersOverview.tsx | 1 + .../UserEditButton/UserEditButton.tsx | 1 + .../AssignGroupsModal/AssignGroupsModal.tsx | 1 + .../VersionUpdateToast/VersionUpdateToast.tsx | 1 + 12 files changed, 284 insertions(+), 6 deletions(-) create mode 100644 e2e/tests/webhook.spec.ts create mode 100644 e2e/utils/controllers/groups.ts create mode 100644 e2e/utils/controllers/webhooks.ts diff --git a/e2e/config.ts b/e2e/config.ts index a9dfbe521..e22bdb88d 100644 --- a/e2e/config.ts +++ b/e2e/config.ts @@ -44,6 +44,8 @@ export const routes = { overview: '/admin/overview', settings: '/admin/settings', devices: '/admin/devices', + groups: '/admin/groups', + webhooks: '/admin/webhooks' }, authorize: '/api/v1/oauth/authorize', }; diff --git a/e2e/tests/auth.spec.ts b/e2e/tests/auth.spec.ts index 59026a199..c9b7ca708 100644 --- a/e2e/tests/auth.spec.ts +++ b/e2e/tests/auth.spec.ts @@ -30,7 +30,7 @@ test.describe('Test user authentication', () => { expect(page.url()).toBe(routes.base + routes.admin.wizard); }); - test('Create user and login as him', async ({ page, browser }) => { + test('Create user and log in as him', async ({ page, browser }) => { await waitForBase(page); await createUser(browser, testUser); await loginBasic(page, testUser); @@ -38,7 +38,7 @@ test.describe('Test user authentication', () => { expect(page.url()).toBe(routes.base + routes.me); }); - test('Login with admin user TOTP', async ({ page, browser }) => { + test('Log in with admin user TOTP', async ({ page, browser }) => { await waitForBase(page); await loginBasic(page, defaultUserAdmin); const { secret } = await enableTOTP(browser, defaultUserAdmin); @@ -48,7 +48,7 @@ test.describe('Test user authentication', () => { await waitForRoute(page, routes.admin.wizard); }); - test('Login with user TOTP', async ({ page, browser }) => { + test('Log in with user TOTP', async ({ page, browser }) => { await waitForBase(page); await createUser(browser, testUser); const { secret } = await enableTOTP(browser, testUser); @@ -69,7 +69,7 @@ test.describe('Test user authentication', () => { expect(page.url()).toBe(routes.base + routes.me); }); - test('Login with Email TOTP', async ({ page, browser }) => { + test('Log in with Email TOTP', async ({ page, browser }) => { await waitForBase(page); await createUser(browser, testUser); const { secret } = await enableEmailMFA(browser, testUser); @@ -84,7 +84,7 @@ test.describe('Test user authentication', () => { await waitForRoute(page, routes.me); }); - test('Login as disabled user', async ({ page, browser }) => { + test('Log in as disabled user', async ({ page, browser }) => { await waitForBase(page); await createUser(browser, testUser); await disableUser(browser, testUser); @@ -110,6 +110,28 @@ test.describe('Test user authentication', () => { await page.locator('a[href="/me"]').click(); await responsePromise; }); + test('Disable user MFA and log in', async ({ page, browser }) => { + await waitForBase(page); + await createUser(browser,testUser); + const { secret } = await enableTOTP(browser, testUser); + await loginBasic(page,defaultUserAdmin); + await page.goto(routes.base + routes.admin.users, { + waitUntil: 'networkidle' + }); + await page.getByTestId('user-2').locator('.user-edit-cell').click(); + await page.getByTestId('disable-mfa-button').click(); + await page.waitForTimeout(800); + await page.getByRole('button', { name: 'Disable MFA' }).click(); + await page.waitForTimeout(800); + await page.goto(routes.base + routes.admin.users+`/${testUser.username}`, { + waitUntil: 'networkidle' + }); + await expect(page.locator('.mfa .status .message')).toHaveText('Disabled'); + await logout(page); + await loginBasic(page,testUser); + await page.waitForTimeout(800); + await expect(page.locator('.mfa .status .message')).toHaveText('Disabled'); + }); }); test.describe('Test password change', () => { diff --git a/e2e/tests/groups.spec.ts b/e2e/tests/groups.spec.ts index 4cbb2acf5..38ff2d57f 100644 --- a/e2e/tests/groups.spec.ts +++ b/e2e/tests/groups.spec.ts @@ -1,11 +1,13 @@ import { expect, test } from '@playwright/test'; -import { routes, testUserTemplate } from '../config'; +import { defaultUserAdmin, routes, testUserTemplate } from '../config'; import { createUser } from '../utils/controllers/createUser'; import { loginBasic } from '../utils/controllers/login'; import { dockerRestart } from '../utils/docker'; import { waitForBase } from '../utils/waitForBase'; import { waitForRoute } from '../utils/waitForRoute'; +import { createGroup } from '../utils/controllers/groups'; +import { waitForPromise } from '../utils/waitForPromise'; test.describe('Test groups', () => { test.beforeEach(() => dockerRestart()); @@ -18,4 +20,42 @@ test.describe('Test groups', () => { await waitForRoute(page, routes.admin.wizard); expect(page.url()).toBe(routes.base + routes.admin.wizard); }); + + test('Bulk assign groups', async ({ page, browser }) => { + + const additionalUsers = [ + { ...testUserTemplate,mail:'test2@test.com', username: 'test2'}, + { ...testUserTemplate,mail:'test3@test.com', username: 'test3'} + ]; + const test_group_name = "test_group"; + await waitForBase(page); + const testUser1 = { ...testUserTemplate,mail:'test1@test.com', username: 'test1'}; + await createUser(browser,testUser1,['Admin']); + + + for (const newuser of additionalUsers){ + await createUser(browser,newuser); + } + await createGroup(browser, true, test_group_name); + await loginBasic(page,defaultUserAdmin); + await page.goto(routes.base + routes.admin.users, { + waitUntil: 'networkidle' + }); + + const userCheckboxes = page.locator('[data-testid^="user-"]').locator('.select-cell'); + const checkboxCount = await page.locator('[data-testid^="user-"]').count(); + for (let i = 0; i < checkboxCount; i++) { + await userCheckboxes.nth(i).click(); + } + await page.getByTestId('group-bulk-assign').click(); + await page.locator('.groups-container').waitFor({ state: 'visible', timeout: 10000 }); + await waitForPromise(2000); + await page.locator('.select-row').nth(2).click(); + await page.getByTestId('confirm-bulk-assign').click(); + await page.locator('.groups-container').waitFor({ state: 'hidden', timeout: 10000 }); + const testGroupElements = page.locator(`.groups-cell .group .text-container:has-text("${test_group_name}")`); + + // 2(default admin + user with admin group) + additional users + await expect(testGroupElements).toHaveCount(2+additionalUsers.length, { timeout: 5000 }); + }); }); diff --git a/e2e/tests/webhook.spec.ts b/e2e/tests/webhook.spec.ts new file mode 100644 index 000000000..5a385a15b --- /dev/null +++ b/e2e/tests/webhook.spec.ts @@ -0,0 +1,152 @@ +import { expect, test } from '@playwright/test'; +import { dockerRestart } from '../utils/docker'; +import { defaultUserAdmin, routes } from '../config'; +import { createWebhook } from '../utils/controllers/webhooks'; +import { loginBasic } from '../utils/controllers/login'; + +test.describe('Test webhooks', () => { + test.beforeEach(async ({ page }) => { + dockerRestart(); + }); + const webhook_url ="https://defguard.defguard/webhook"; + const webhook_description="example webhook"; + const webhook_secret="secret"; + + + test('Create webhook and verify content', async ({ page, browser }) => { + await createWebhook(browser, webhook_url, webhook_description, webhook_secret); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.admin.webhooks, { + waitUntil: 'networkidle' + }); + await page.waitForTimeout(2000); + + const webhookRow = page.locator('.default-row'); + + const webhook_url_cell = await webhookRow.locator('.cell-0 span').textContent(); + expect(webhook_url_cell).toBe(webhook_url); + + const webhook_description_cell = await webhookRow.locator('.cell-1 span').textContent(); + expect(webhook_description_cell).toBe(webhook_description); + + const webhook_state_cell = await webhookRow.locator('.cell-2 span').textContent(); + expect(webhook_state_cell).toBe('Enabled'); + + const editButton = webhookRow.locator('.cell-3 .edit-button'); + await expect(editButton).toBeVisible(); + }); + + + const new_webhook_url ="https://changed.defguard/webhook"; + const new_webhook_description="changed webhook"; + test('Create, modify webhook and verify content', async ({ page, browser }) => { + await createWebhook(browser, webhook_url, webhook_description, "secret"); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.admin.webhooks, { + waitUntil: 'networkidle' + }); + await page.waitForTimeout(2000); + + const webhookRow = page.locator('.default-row'); + + const webhook_url_cell = await webhookRow.locator('.cell-0 span').textContent(); + expect(webhook_url_cell).toBe(webhook_url); + + const webhook_description_cell = await webhookRow.locator('.cell-1 span').textContent(); + expect(webhook_description_cell).toBe(webhook_description); + + const webhook_state_cell = await webhookRow.locator('.cell-2 span').textContent(); + expect(webhook_state_cell).toBe('Enabled'); + + const editButton = webhookRow.locator('.cell-3 .edit-button'); + await expect(editButton).toBeVisible(); + // check if webhook is OK + // then edit webhook + await webhookRow.locator('.cell-3 .edit-button').click(); + + await page.locator('.edit-button-floating-ui').getByRole('button', { name: 'Edit' }).click(); + + await page.getByTestId('field-url').fill(new_webhook_url); + await page.getByTestId('field-description').fill(new_webhook_description); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.waitForTimeout(1000); + + const changed_webhookRow = page.locator('.default-row'); + const changed_webhook_url_cell = await changed_webhookRow.locator('.cell-0 span').textContent(); + expect(changed_webhook_url_cell).toBe(new_webhook_url); + + const changed_webhook_description_cell = await changed_webhookRow.locator('.cell-1 span').textContent(); + expect(changed_webhook_description_cell).toBe(new_webhook_description); + + const changed_webhook_state_cell = await changed_webhookRow.locator('.cell-2 span').textContent(); + expect(changed_webhook_state_cell).toBe('Enabled'); + + }); + + test('Create webhook, change state and verify content', async ({ page, browser }) => { + await createWebhook(browser, webhook_url, webhook_description, webhook_secret); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.admin.webhooks, { + waitUntil: 'networkidle' + }); + await page.waitForTimeout(2000); + const webhookRow = page.locator('.default-row'); + await webhookRow.locator('.cell-3 .edit-button').click(); + await page.locator('.edit-button-floating-ui').getByRole('button', { name: 'Disable' }).click(); + await page.waitForTimeout(2000); + + + + // is everything ok after changing state to Disabled? + const webhook_url_cell = await webhookRow.locator('.cell-0 span').textContent(); + expect(webhook_url_cell).toBe(webhook_url); + + const webhook_description_cell = await webhookRow.locator('.cell-1 span').textContent(); + expect(webhook_description_cell).toBe(webhook_description); + + const webhook_state_cell = await webhookRow.locator('.cell-2 span').textContent(); + expect(webhook_state_cell).toBe('Disabled'); + + const editButton = webhookRow.locator('.cell-3 .edit-button'); + await expect(editButton).toBeVisible(); + + await webhookRow.locator('.cell-3 .edit-button').click(); + await page.locator('.edit-button-floating-ui').getByRole('button', { name: 'Enable' }).click(); + await page.waitForTimeout(2000); + + // is everything ok after changing state to Enabled? + const changed_webhook_url_cell = await webhookRow.locator('.cell-0 span').textContent(); + expect(changed_webhook_url_cell).toBe(webhook_url); + + const changed_webhook_description_cell = await webhookRow.locator('.cell-1 span').textContent(); + expect(changed_webhook_description_cell).toBe(webhook_description); + + const changed_webhook_state_cell = await webhookRow.locator('.cell-2 span').textContent(); + expect(changed_webhook_state_cell).toBe('Enabled'); + + const changed_editButton = webhookRow.locator('.cell-3 .edit-button'); + await expect(changed_editButton).toBeVisible(); + }); + test('Create webhook and delete it', async ({ page, browser }) => { + await createWebhook(browser, webhook_url, webhook_description, webhook_secret); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.admin.webhooks, { + waitUntil: 'networkidle' + }); + await page.waitForTimeout(2000); + + const webhookRow = page.locator('.default-row'); + const editButton = webhookRow.locator('.cell-3 .edit-button'); + await expect(editButton).toBeVisible(); + // check if webhook is OK + // then edit webhook + await webhookRow.locator('.cell-3 .edit-button').click(); + await page.locator('.edit-button-floating-ui').getByRole('button', { name: 'Delete webhook' }).click(); + await page.locator('.modal-content').waitFor({ state: 'visible', timeout: 5000 }); + await page.getByRole('button', { name: 'Delete' }).click(); + await page.waitForTimeout(2000); + const webhookRows = page.locator('.default-row'); + await expect(webhookRows).toHaveCount(0); + + }); +}); \ No newline at end of file diff --git a/e2e/utils/controllers/groups.ts b/e2e/utils/controllers/groups.ts new file mode 100644 index 000000000..1d30ea32a --- /dev/null +++ b/e2e/utils/controllers/groups.ts @@ -0,0 +1,21 @@ +import { Browser } from 'playwright'; +import { waitForBase } from '../waitForBase'; +import { loginBasic } from './login'; +import { defaultUserAdmin, routes } from '../../config'; + + +export const createGroup = async ( + browser: Browser, + is_admin: Boolean, + group_name: string +): Promise => { + const context = await browser.newContext(); + const page = await context.newPage(); + await waitForBase(page); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.admin.groups); + await page.getByTestId('add-group').click(); + await page.getByTestId('field-name').fill(group_name); + await page.getByTestId('submit-group').click(); + await context.close(); +}; diff --git a/e2e/utils/controllers/webhooks.ts b/e2e/utils/controllers/webhooks.ts new file mode 100644 index 000000000..e8152084c --- /dev/null +++ b/e2e/utils/controllers/webhooks.ts @@ -0,0 +1,35 @@ +import { Browser } from 'playwright'; +import { waitForBase } from '../waitForBase'; +import { loginBasic } from './login'; +import { defaultUserAdmin, routes } from '../../config'; + + +export const createWebhook = async ( + browser: Browser, + url: string, + description: string, + secret_token?: string +): Promise => { + const context = await browser.newContext(); + const page = await context.newPage(); + await waitForBase(page); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.admin.webhooks); + await page.getByRole('button', { name: 'Add new' }).click(); + await page.waitForTimeout(800); + await page.getByTestId('field-url').fill(url); + await page.getByTestId('field-description').fill(description); + if (secret_token){ + await page.getByTestId('field-token').fill(secret_token); + } + else{ + await page.getByTestId('field-token').fill(" "); + } + await page.getByTestId('field-on_user_created').click(); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.waitForTimeout(2000); + + + await context.close(); +}; diff --git a/web/src/pages/groups/components/GroupsManagement/GroupsManagement.tsx b/web/src/pages/groups/components/GroupsManagement/GroupsManagement.tsx index dfbb86a76..5f561b4ba 100644 --- a/web/src/pages/groups/components/GroupsManagement/GroupsManagement.tsx +++ b/web/src/pages/groups/components/GroupsManagement/GroupsManagement.tsx @@ -48,6 +48,7 @@ export const GroupsManagement = () => { icon={} text="Add new" onClick={() => openGroupModal()} + data-testid="add-group" /> {data && } diff --git a/web/src/pages/groups/components/modals/AddGroupModal/AddGroupModal.tsx b/web/src/pages/groups/components/modals/AddGroupModal/AddGroupModal.tsx index 2dc07e45d..a2fd5c0c6 100644 --- a/web/src/pages/groups/components/modals/AddGroupModal/AddGroupModal.tsx +++ b/web/src/pages/groups/components/modals/AddGroupModal/AddGroupModal.tsx @@ -210,6 +210,7 @@ const ModalContent = () => { text={localLL.submit()} styleVariant={ButtonStyleVariant.PRIMARY} type="submit" + data-testid="submit-group" /> diff --git a/web/src/pages/users/UsersOverview/UsersOverview.tsx b/web/src/pages/users/UsersOverview/UsersOverview.tsx index 3d74557ca..71b6bc61c 100644 --- a/web/src/pages/users/UsersOverview/UsersOverview.tsx +++ b/web/src/pages/users/UsersOverview/UsersOverview.tsx @@ -197,6 +197,7 @@ export const UsersOverview = () => { styleVariant={ButtonStyleVariant.PRIMARY} text="Assign group to selected users" onClick={() => openGroupsAssign(selectedUsers)} + data-testid="group-bulk-assign" /> )} {breakpoint === 'desktop' && ( diff --git a/web/src/pages/users/UsersOverview/components/UserEditButton/UserEditButton.tsx b/web/src/pages/users/UsersOverview/components/UserEditButton/UserEditButton.tsx index 2f9e164d0..dd9560f4b 100644 --- a/web/src/pages/users/UsersOverview/components/UserEditButton/UserEditButton.tsx +++ b/web/src/pages/users/UsersOverview/components/UserEditButton/UserEditButton.tsx @@ -47,6 +47,7 @@ export const UserEditButton = ({ user }: Props) => { key="disable-mfa" text={LL.usersOverview.list.editButton.disableMfa()} onClick={() => openDisableMfaModal(user)} + data-testid="disable-mfa-button" styleVariant={EditButtonOptionStyleVariant.WARNING} /> )} diff --git a/web/src/pages/users/UsersOverview/modals/AssignGroupsModal/AssignGroupsModal.tsx b/web/src/pages/users/UsersOverview/modals/AssignGroupsModal/AssignGroupsModal.tsx index 578d79278..607259a60 100644 --- a/web/src/pages/users/UsersOverview/modals/AssignGroupsModal/AssignGroupsModal.tsx +++ b/web/src/pages/users/UsersOverview/modals/AssignGroupsModal/AssignGroupsModal.tsx @@ -156,6 +156,7 @@ const ModalContent = () => { disabled={selected.length === 0} loading={isPending} onClick={() => handleSubmit()} + data-testid="confirm-bulk-assign" /> diff --git a/web/src/shared/components/Layout/VersionUpdateToast/VersionUpdateToast.tsx b/web/src/shared/components/Layout/VersionUpdateToast/VersionUpdateToast.tsx index 79d40db00..9abd0a841 100644 --- a/web/src/shared/components/Layout/VersionUpdateToast/VersionUpdateToast.tsx +++ b/web/src/shared/components/Layout/VersionUpdateToast/VersionUpdateToast.tsx @@ -100,6 +100,7 @@ export const VersionUpdateToast = ({ id }: ToastOptions) => { {LL.modals.updatesNotificationToaster.controls.more()}