mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
chore(test): add M2M RBAC console test (#4598)
* chore(test): add m2m rbac console test * fix: fix m2m rbac console ui test
This commit is contained in:
parent
a8b5a020fd
commit
c11fafd72c
4 changed files with 381 additions and 9 deletions
|
@ -25,7 +25,7 @@ export const expectFrameworksInGroup = async (page: Page, groupSelector: string)
|
||||||
/* eslint-enable no-await-in-loop */
|
/* eslint-enable no-await-in-loop */
|
||||||
};
|
};
|
||||||
|
|
||||||
export const expectToClickFramework = async (page: Page, framework: string) => {
|
export const expectToChooseAndClickApplicationFramework = async (page: Page, framework: string) => {
|
||||||
const frameworkCard = await expect(page).toMatchElement(
|
const frameworkCard = await expect(page).toMatchElement(
|
||||||
'div[class*=card]:has(div[class$=header] div[class$=name])',
|
'div[class*=card]:has(div[class$=header] div[class$=name])',
|
||||||
{
|
{
|
||||||
|
@ -42,9 +42,9 @@ export const expectFrameworkExists = async (page: Page, framework: string) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const expectToProceedCreationFrom = async (
|
export const expectToProceedApplicationCreationFrom = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
{ name, description }: ApplicationCase
|
{ name, description }: { name: string; description: string }
|
||||||
) => {
|
) => {
|
||||||
// Expect the creation form to be open
|
// Expect the creation form to be open
|
||||||
await expectModalWithTitle(page, 'Create Application');
|
await expectModalWithTitle(page, 'Create Application');
|
||||||
|
|
|
@ -20,8 +20,8 @@ import {
|
||||||
} from './constants.js';
|
} from './constants.js';
|
||||||
import {
|
import {
|
||||||
expectFrameworkExists,
|
expectFrameworkExists,
|
||||||
expectToClickFramework,
|
expectToChooseAndClickApplicationFramework,
|
||||||
expectToProceedCreationFrom,
|
expectToProceedApplicationCreationFrom,
|
||||||
expectToProceedSdkGuide,
|
expectToProceedSdkGuide,
|
||||||
expectToProceedAppDeletion,
|
expectToProceedAppDeletion,
|
||||||
expectFrameworksInGroup,
|
expectFrameworksInGroup,
|
||||||
|
@ -54,9 +54,9 @@ describe('applications', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('create the initial application from the table placeholder', async () => {
|
it('create the initial application from the table placeholder', async () => {
|
||||||
await expectToClickFramework(page, initialApp.framework);
|
await expectToChooseAndClickApplicationFramework(page, initialApp.framework);
|
||||||
|
|
||||||
await expectToProceedCreationFrom(page, initialApp);
|
await expectToProceedApplicationCreationFrom(page, initialApp);
|
||||||
|
|
||||||
await expectToProceedSdkGuide(page, initialApp, true);
|
await expectToProceedSdkGuide(page, initialApp, true);
|
||||||
|
|
||||||
|
@ -126,9 +126,9 @@ describe('applications', () => {
|
||||||
// Expect the framework exists after filtering
|
// Expect the framework exists after filtering
|
||||||
await expectFrameworkExists(page, testApp.framework);
|
await expectFrameworkExists(page, testApp.framework);
|
||||||
|
|
||||||
await expectToClickFramework(page, testApp.framework);
|
await expectToChooseAndClickApplicationFramework(page, testApp.framework);
|
||||||
|
|
||||||
await expectToProceedCreationFrom(page, testApp);
|
await expectToProceedApplicationCreationFrom(page, testApp);
|
||||||
|
|
||||||
await expectToProceedSdkGuide(page, testApp);
|
await expectToProceedSdkGuide(page, testApp);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,306 @@
|
||||||
|
import { logtoConsoleUrl as logtoConsoleUrlString } from '#src/constants.js';
|
||||||
|
import {
|
||||||
|
expectConfirmModalAndAct,
|
||||||
|
expectModalWithTitle,
|
||||||
|
expectToClickModalAction,
|
||||||
|
goToAdminConsole,
|
||||||
|
waitForToast,
|
||||||
|
} from '#src/ui-helpers/index.js';
|
||||||
|
import {
|
||||||
|
expectNavigation,
|
||||||
|
appendPathname,
|
||||||
|
generateResourceName,
|
||||||
|
generateResourceIndicator,
|
||||||
|
generateScopeName,
|
||||||
|
generateRoleName,
|
||||||
|
} from '#src/utils.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
expectToChooseAndClickApplicationFramework,
|
||||||
|
expectToProceedApplicationCreationFrom,
|
||||||
|
} from '../applications/helpers.js';
|
||||||
|
|
||||||
|
import { createM2mRoleAndAssignPermissions } from './utils.js';
|
||||||
|
|
||||||
|
await page.setViewport({ width: 1920, height: 1080 });
|
||||||
|
|
||||||
|
describe('M2M RBAC', () => {
|
||||||
|
const logtoConsoleUrl = new URL(logtoConsoleUrlString);
|
||||||
|
const managementApiResourceName = 'Logto Management API';
|
||||||
|
const managementApiPermission = 'all';
|
||||||
|
const apiResourceName = generateResourceName();
|
||||||
|
const apiResourceIndicator = generateResourceIndicator();
|
||||||
|
const permissionName = generateScopeName();
|
||||||
|
const permissionDescription = 'Dummy permission description';
|
||||||
|
const roleName = generateRoleName();
|
||||||
|
const roleDescription = 'Dummy role description';
|
||||||
|
|
||||||
|
const rbacTestAppname = 'm2m-app-001';
|
||||||
|
const m2mFramework = 'Machine-to-machine';
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await goToAdminConsole();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create api resource and permissions', () => {
|
||||||
|
it('navigate to api resources page', async () => {
|
||||||
|
await expectNavigation(
|
||||||
|
page.goto(appendPathname('/console/api-resources', logtoConsoleUrl).href)
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(page).toMatchElement(
|
||||||
|
'div[class$=main] div[class$=headline] div[class$=titleEllipsis]',
|
||||||
|
{
|
||||||
|
text: 'API Resources',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create an api resource', async () => {
|
||||||
|
await expect(page).toClick('div[class$=headline] button span', {
|
||||||
|
text: 'Create API Resource',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectModalWithTitle(page, 'Start with tutorials');
|
||||||
|
|
||||||
|
// Click bottom button to skip tutorials
|
||||||
|
await expect(page).toClick('.ReactModalPortal nav[class$=actionBar] button span', {
|
||||||
|
text: 'Continue without tutorial',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectModalWithTitle(page, 'Create API Resource');
|
||||||
|
|
||||||
|
await expect(page).toFillForm('.ReactModalPortal form', {
|
||||||
|
name: apiResourceName,
|
||||||
|
indicator: apiResourceIndicator,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectToClickModalAction(page, 'Create API Resource');
|
||||||
|
|
||||||
|
await waitForToast(page, {
|
||||||
|
text: `The API resource ${apiResourceName} has been successfully created`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page).toMatchElement('div[class$=header] div[class$=info] div[class$=name]', {
|
||||||
|
text: apiResourceName,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create permission for api resource', async () => {
|
||||||
|
await expect(page).toClick('nav div[class$=item] div[class$=link] a', {
|
||||||
|
text: 'Permissions',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page).toClick('div[class$=filter] button[class$=createButton] span', {
|
||||||
|
text: 'Create Permission',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectModalWithTitle(page, 'Create permission');
|
||||||
|
|
||||||
|
await expect(page).toFillForm('.ReactModalPortal form', {
|
||||||
|
name: permissionName,
|
||||||
|
description: permissionDescription,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectToClickModalAction(page, 'Create permission');
|
||||||
|
|
||||||
|
await waitForToast(page, {
|
||||||
|
text: `The permission ${permissionName} has been successfully created`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page).toMatchElement('table tbody tr td div[class$=name]', {
|
||||||
|
text: permissionName,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create m2m app', () => {
|
||||||
|
it('navigate to applications page', async () => {
|
||||||
|
await expectNavigation(
|
||||||
|
page.goto(appendPathname('/console/applications', logtoConsoleUrl).href)
|
||||||
|
);
|
||||||
|
await expect(page).toMatchElement(
|
||||||
|
'div[class$=main] div[class$=headline] div[class$=titleEllipsis]',
|
||||||
|
{
|
||||||
|
text: 'Applications',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create a m2m app for rbac testing', async () => {
|
||||||
|
await expectToChooseAndClickApplicationFramework(page, m2mFramework);
|
||||||
|
|
||||||
|
await expectToProceedApplicationCreationFrom(page, {
|
||||||
|
name: rbacTestAppname,
|
||||||
|
description: rbacTestAppname,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Skip guide
|
||||||
|
await page.keyboard.press('Escape');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create m2m role', () => {
|
||||||
|
it('navigate to roles page', async () => {
|
||||||
|
await expectNavigation(page.goto(appendPathname('/console/roles', logtoConsoleUrl).href));
|
||||||
|
|
||||||
|
await expect(page).toMatchElement(
|
||||||
|
'div[class$=main] div[class$=headline] div[class$=titleEllipsis]',
|
||||||
|
{
|
||||||
|
text: 'Roles',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create a m2m role and assign permissions to the role', async () => {
|
||||||
|
await createM2mRoleAndAssignPermissions(page, { roleName, roleDescription }, [
|
||||||
|
{ apiResourceName, permissionName },
|
||||||
|
{ apiResourceName: managementApiResourceName, permissionName: managementApiPermission },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('delete a permission from a role on the role details page', async () => {
|
||||||
|
await expect(page).toClick('nav div[class$=item] div[class$=link] a', {
|
||||||
|
text: 'Permissions',
|
||||||
|
});
|
||||||
|
|
||||||
|
const permissionRow = await expect(page).toMatchElement(
|
||||||
|
'table tbody tr:has(td div[class$=name])',
|
||||||
|
{ text: permissionName }
|
||||||
|
);
|
||||||
|
await expect(permissionRow).toClick('td[class$=deleteColumn] button');
|
||||||
|
|
||||||
|
await expectConfirmModalAndAct(page, {
|
||||||
|
title: 'Reminder',
|
||||||
|
actionText: 'Remove',
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForToast(page, {
|
||||||
|
text: `The permission "${permissionName}" was successfully removed from this role`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assign a permission to a role on the role details page', async () => {
|
||||||
|
await expect(page).toClick('div[class$=filter] button span', {
|
||||||
|
text: 'Assign Permissions',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectModalWithTitle(page, 'Assign permissions');
|
||||||
|
|
||||||
|
await expect(page).toClick(
|
||||||
|
'.ReactModalPortal div[class$=resourceItem] div[class$=title] div[class$=name]',
|
||||||
|
{
|
||||||
|
text: apiResourceName,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(page).toClick(
|
||||||
|
'.ReactModalPortal div[class$=resourceItem] div[class$=sourceScopeItem] div[role=button]',
|
||||||
|
{
|
||||||
|
text: permissionName,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await expectToClickModalAction(page, 'Assign Permissions');
|
||||||
|
|
||||||
|
await waitForToast(page, {
|
||||||
|
text: 'The selected permissions were successfully assigned to this role',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page).toMatchElement('table tbody tr:has(td div[class$=name])', {
|
||||||
|
text: permissionName,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('assign a role to a m2m app (on role details page)', () => {
|
||||||
|
it('assign a role to a m2m app on the role details page', async () => {
|
||||||
|
await expect(page).toClick('nav div[class$=item] div[class$=link] a', {
|
||||||
|
text: 'Machine-to-machine apps',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page).toClick('div[class$=filter] button span', {
|
||||||
|
text: 'Assign applications',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectModalWithTitle(page, 'Assign apps');
|
||||||
|
|
||||||
|
await expect(page).toClick(
|
||||||
|
'.ReactModalPortal div[class$=rolesTransfer] div[class$=item] div[class$=title]',
|
||||||
|
{
|
||||||
|
text: rbacTestAppname,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await expectToClickModalAction(page, 'Assign applications');
|
||||||
|
|
||||||
|
await waitForToast(page, {
|
||||||
|
text: 'The selected applications were successfully assigned to this role',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page).toMatchElement(
|
||||||
|
'div[class$=applicationsTable] td div[class$=item] a[class$=title]',
|
||||||
|
{
|
||||||
|
text: rbacTestAppname,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('assign/remove a role to/from a m2m app (on m2m app details page)', () => {
|
||||||
|
it('remove a role form a m2m app on the app details page', async () => {
|
||||||
|
// Navigate to app details page
|
||||||
|
await expect(page).toClick('table tbody tr td a[class$=title]', {
|
||||||
|
text: rbacTestAppname,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page).toMatchElement(
|
||||||
|
'div[class$=header] > div[class$=metadata] div[class$=name]',
|
||||||
|
{
|
||||||
|
text: rbacTestAppname,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Go to roles tab
|
||||||
|
await expect(page).toClick('nav div[class$=item] div[class$=link] a', {
|
||||||
|
text: 'Roles',
|
||||||
|
});
|
||||||
|
|
||||||
|
const roleRow = await expect(page).toMatchElement('table tbody tr:has(td a[class$=title])', {
|
||||||
|
text: roleName,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click remove button
|
||||||
|
await expect(roleRow).toClick('td:last-of-type button');
|
||||||
|
|
||||||
|
await expectConfirmModalAndAct(page, {
|
||||||
|
title: 'Reminder',
|
||||||
|
actionText: 'Remove',
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForToast(page, {
|
||||||
|
text: `${roleName} was successfully removed from this user.`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('add a role to m2m app on the application details page', async () => {
|
||||||
|
await expect(page).toClick('div[class$=filter] button span', {
|
||||||
|
text: 'Assign roles',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectModalWithTitle(page, `Assign roles to ${rbacTestAppname}`);
|
||||||
|
|
||||||
|
await expect(page).toClick(
|
||||||
|
'.ReactModalPortal div[class$=rolesTransfer] div[class$=item] div[class$=name]',
|
||||||
|
{
|
||||||
|
text: roleName,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await expectToClickModalAction(page, 'Assign roles');
|
||||||
|
|
||||||
|
await waitForToast(page, {
|
||||||
|
text: 'Successfully assigned role(s)',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { type Page } from 'puppeteer';
|
||||||
|
|
||||||
|
import {
|
||||||
|
expectModalWithTitle,
|
||||||
|
expectToClickModalAction,
|
||||||
|
waitForToast,
|
||||||
|
} from '#src/ui-helpers/index.js';
|
||||||
|
|
||||||
|
export const createM2mRoleAndAssignPermissions = async (
|
||||||
|
page: Page,
|
||||||
|
{ roleName, roleDescription }: { roleName: string; roleDescription: string },
|
||||||
|
apiResources: Array<{ apiResourceName: string; permissionName: string }>
|
||||||
|
) => {
|
||||||
|
await expect(page).toClick('div[class$=headline] button span', {
|
||||||
|
text: 'Create Role',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectModalWithTitle(page, 'Create Role');
|
||||||
|
|
||||||
|
// Expand role type selection
|
||||||
|
await expect(page).toClick('button[class$=roleTypeSelectionSwitch] span', {
|
||||||
|
text: 'Show more options',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page).toClick('div[class*=radioGroup][class$=roleTypes] div[class$=content]', {
|
||||||
|
text: 'Machine-to-machine app role',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page).toFillForm('.ReactModalPortal form', {
|
||||||
|
name: roleName,
|
||||||
|
description: roleDescription,
|
||||||
|
});
|
||||||
|
|
||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
for (const apiResource of apiResources) {
|
||||||
|
const { apiResourceName, permissionName } = apiResource;
|
||||||
|
// Assign customized permission
|
||||||
|
await expect(page).toClick(
|
||||||
|
'.ReactModalPortal div[class$=resourceItem] div[class$=title] div[class$=name]',
|
||||||
|
{
|
||||||
|
text: apiResourceName,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(page).toClick(
|
||||||
|
'.ReactModalPortal div[class$=resourceItem] div[class$=sourceScopeItem] div[role=button]',
|
||||||
|
{
|
||||||
|
text: permissionName,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* eslint-enable no-await-in-loop */
|
||||||
|
|
||||||
|
await expectToClickModalAction(page, 'Create Role');
|
||||||
|
|
||||||
|
await waitForToast(page, {
|
||||||
|
text: `The role ${roleName} has been successfully created.`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectModalWithTitle(page, 'Assign apps');
|
||||||
|
await expectToClickModalAction(page, 'Skip for now');
|
||||||
|
|
||||||
|
await expect(page).toMatchElement('div[class$=header] div[class$=info] div[class$=name]', {
|
||||||
|
text: roleName,
|
||||||
|
});
|
||||||
|
};
|
Loading…
Reference in a new issue