Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions e2e/navigation/legacy-route-redirects.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* SPDX-FileCopyrightText: 2026 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import {login} from 'e2e/credentials';
import {expect, test} from 'e2e/fixtures';

const compatibilityRoutes = [
{legacy: '/auditreports', canonical: '/audit-reports'},
{legacy: '/operatingsystems', canonical: '/operating-systems'},
{legacy: '/scanconfigs', canonical: '/scan-configs'},
];

const canonicalRoutes = compatibilityRoutes.map(({canonical}) => canonical);

test.describe('canonical routes', () => {
test.beforeEach(async ({page}) => {
await login(page);
});

for (const route of canonicalRoutes) {
test(`${route} stays on its canonical URL`, async ({page}) => {
await page.goto(route);

await expect(page).toHaveURL(new RegExp(`${route}$`));
await expect(page).not.toHaveURL(/\/login(?:$|\?)/);
});
}
});

test.describe('legacy route redirects', () => {
test.beforeEach(async ({page}) => {
await login(page);
});

for (const {legacy, canonical} of compatibilityRoutes) {
test(`${legacy} redirects to ${canonical}`, async ({page}) => {
await page.goto(legacy);

await expect(page).toHaveURL(new RegExp(`${canonical}$`));
await expect(page).not.toHaveURL(/\/login(?:$|\?)/);
});
}
});
5 changes: 4 additions & 1 deletion e2e/navigation/menu-links.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ const expandSidebarSections = async (page: Page) => {
for (const label of topLevelMenuLabels) {
const section = page.getByRole('link', {name: label, exact: true}).first();
if ((await section.count()) > 0) {
await section.click();
// Top-level menu entries are placeholder links. Their parent owns the
// expand action, while clicking the link itself navigates and remounts
// the menu before its expanded state can persist.
await section.locator('..').click();
}
}
};
Expand Down
13 changes: 8 additions & 5 deletions e2e/users/users-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ const selectMultipleItems = async (
page: Page,
groupTitle: string,
numberOfItems: number,
expectedLabels?: string[],
) => {
const formGroup = page
.locator('[data-testid="form-group"]')
Expand All @@ -174,14 +175,16 @@ const selectMultipleItems = async (
const input = formGroup.locator('[data-testid="multi-select"]').first();

await expect(input).toBeVisible();
await expect(input).toBeEnabled();
await input.click();

const options = page.getByRole('option');
await expect(options.nth(numberOfItems - 1)).toBeVisible();

const labels: string[] = [];
for (let index = 0; index < numberOfItems; index += 1) {
labels.push(await options.nth(index).innerText());
const labels = expectedLabels ?? [];
if (labels.length === 0) {
await expect(options.nth(numberOfItems - 1)).toBeVisible();
for (let index = 0; index < numberOfItems; index += 1) {
labels.push(await options.nth(index).innerText());
}
}

for (const label of labels) {
Expand Down
32 changes: 31 additions & 1 deletion e2e/users/users-list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,24 @@ test.describe('users page flows', () => {
page,
}) => {
const userName = createUniqueUserName('e2e-multiple-assignments');
const groupNames = [
createUniqueUserName('e2e-user-group'),
createUniqueUserName('e2e-user-group'),
];

await page.goto('/groups');
await expect(page).not.toHaveURL(/\/login(?:$|\?)/);
for (const groupName of groupNames) {
await page.getByTitle('New Group').click();
await page.locator('input[name="name"]').fill(groupName);
await page.getByTestId('dialog-save-button').click();
await expect(page.getByRole('dialog')).toHaveCount(0);
await expect(
page.getByRole('row', {name: new RegExp(groupName)}),
).toBeVisible();
}

await gotoUsersPage(page);
await openListCreateDialog(page);
await createUserFromCurrentPage(page, userName);

Expand All @@ -117,7 +134,12 @@ test.describe('users page flows', () => {
await expect(page.locator('input[name="name"]')).toBeVisible();

const roleLabels = await selectMultipleItems(page, 'Roles', 2);
const groupLabels = await selectMultipleItems(page, 'Groups', 2);
const groupLabels = await selectMultipleItems(
page,
'Groups',
groupNames.length,
groupNames,
);
await saveUserDialog(page);

await applyFilter(page, `name=${userName}`);
Expand All @@ -141,6 +163,14 @@ test.describe('users page flows', () => {

await closeTopDialog(page);
await deleteSingleUserFromList(page, userName);
await page.goto('/groups');
for (const groupName of groupNames) {
const groupRow = page
.getByRole('row', {name: new RegExp(groupName)})
.first();
await groupRow.getByTitle('Move Group to trashcan').click();
await expect(groupRow).toHaveCount(0);
}
await resetFilter(page);
});

Expand Down
24 changes: 3 additions & 21 deletions src/gmp/utils/entity-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import {_l, _} from 'gmp/locale/lang';
import logger from 'gmp/log';
import {isDefined} from 'gmp/utils/identity';
import {entityURL as entityRouteURL} from 'web/route-paths';

export interface WithEntityType {
entityType: EntityType;
Expand Down Expand Up @@ -245,24 +246,5 @@ export const resourceType = (type?: EntityType): string | undefined => {
return apiType(type);
};

const ENTITY_URLS = {
agentgroup: 'agent-group',
agentinstaller: 'agent-installer',
auditreport: 'audit-report',
certbund: 'cert-bund-advisory',
dfncert: 'dfn-cert-advisory',
ociimagetarget: 'oci-image-target',
operatingsystem: 'operating-system',
webapplicationtarget: 'web-application-target',
portlist: 'port-list',
portrange: 'port-range',
reportconfig: 'report-config',
reportformat: 'report-format',
scanconfig: 'scan-config',
tlscertificate: 'tls-certificate',
} as Record<EntityType, string>;

export const entityURL = (type: EntityType, id: string): string => {
const urlType = ENTITY_URLS[type] ?? type;
return `/${urlType}/${encodeURIComponent(id)}`;
};
export const entityURL = (type: EntityType, id: string): string =>
entityRouteURL(type, id);
3 changes: 2 additions & 1 deletion src/web/Authorized.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {useLocation} from 'react-router';
import useGmp from 'web/hooks/useGmp';
import useUserIsLoggedIn from 'web/hooks/useUserIsLoggedIn';
import useUserName from 'web/hooks/useUserName';
import {ROUTES} from 'web/route-paths';
import {saveLastVisitedPage} from 'web/utils/user-last-visited-page';

interface AuthorizedProps {
Expand All @@ -22,7 +23,7 @@ const Authorized = ({children}: AuthorizedProps) => {
const username = useUserName();

const logout = useCallback(() => {
if (username && location.pathname !== '/login') {
if (username && location.pathname !== ROUTES.login.url) {
const currentPath = `${location.pathname}${location.search ?? ''}`;
saveLastVisitedPage(username, currentPath);
}
Expand Down
Loading
Loading