mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
test(console): assign permissions to organization role (#5729)
This commit is contained in:
parent
e1d5b8a72a
commit
45157873a2
4 changed files with 374 additions and 3 deletions
|
@ -0,0 +1,90 @@
|
|||
import ExpectApiResources from '#src/ui-helpers/expect-api-resources.js';
|
||||
import ExpectConsole from '#src/ui-helpers/expect-console.js';
|
||||
import ExpectOrganizations from '#src/ui-helpers/expect-organizations.js';
|
||||
import {
|
||||
generateResourceIndicator,
|
||||
generateResourceName,
|
||||
generateRoleName,
|
||||
generateScopeName,
|
||||
} from '#src/utils.js';
|
||||
|
||||
const expectConsole = new ExpectConsole();
|
||||
const expectApiResources = new ExpectApiResources();
|
||||
const expectOrganizations = new ExpectOrganizations();
|
||||
|
||||
const apiResourceName = generateResourceName();
|
||||
const apiResourceIndicator = generateResourceIndicator();
|
||||
const apiPermissionName = generateScopeName();
|
||||
|
||||
const organizationPermissionName = generateScopeName();
|
||||
const organizationRoleName = generateRoleName();
|
||||
|
||||
const dummyPermissionDescription = 'Dummy permission description';
|
||||
|
||||
describe('Organization RBAC', () => {
|
||||
beforeAll(async () => {
|
||||
await expectConsole.start();
|
||||
});
|
||||
|
||||
it('navigates to API resources page', async () => {
|
||||
await expectApiResources.gotoPage('/api-resources', 'API resources');
|
||||
await expectApiResources.toExpectTableHeaders('API name', 'API Identifier');
|
||||
});
|
||||
|
||||
it('creates an API resource', async () => {
|
||||
await expectApiResources.toCreateApiResource({
|
||||
name: apiResourceName,
|
||||
indicator: apiResourceIndicator,
|
||||
});
|
||||
});
|
||||
|
||||
it('creates an API permission for organization role', async () => {
|
||||
await expectApiResources.toCreateApiResourcePermission(
|
||||
{ name: apiPermissionName, description: dummyPermissionDescription },
|
||||
apiResourceName
|
||||
);
|
||||
});
|
||||
|
||||
it('navigates to the organization template', async () => {
|
||||
await expectConsole.gotoPage('/organization-template', 'Organization template');
|
||||
await expectConsole.toExpectTabs('Organization roles', 'Organization permissions');
|
||||
await expectConsole.toExpectTableHeaders('Organization Role', 'Permissions');
|
||||
});
|
||||
|
||||
it('creates an organization permission', async () => {
|
||||
await expectOrganizations.toCreateOrganizationPermission({
|
||||
name: organizationPermissionName,
|
||||
description: dummyPermissionDescription,
|
||||
});
|
||||
});
|
||||
|
||||
it('creates an organization role', async () => {
|
||||
await expectOrganizations.toCreateOrganizationRole({
|
||||
name: organizationRoleName,
|
||||
description: dummyPermissionDescription,
|
||||
});
|
||||
});
|
||||
|
||||
it('assigns organization permissions and API permissions for organization role', async () => {
|
||||
await expectOrganizations.toAssignPermissionsForOrganizationRole({
|
||||
organizationPermission: organizationPermissionName,
|
||||
apiPermission: {
|
||||
resource: apiResourceName,
|
||||
permission: apiPermissionName,
|
||||
},
|
||||
forOrganizationRole: organizationRoleName,
|
||||
});
|
||||
});
|
||||
|
||||
// Clean up
|
||||
it('deletes created resources', async () => {
|
||||
// Delete created organization role
|
||||
await expectOrganizations.toDeleteOrganizationRole(organizationRoleName);
|
||||
|
||||
// Delete created organization permissions
|
||||
await expectOrganizations.toDeleteOrganizationPermission(organizationPermissionName);
|
||||
|
||||
// Delete created API resource
|
||||
await expectApiResources.toDeleteApiResource(apiResourceName);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,105 @@
|
|||
import { cls } from '#src/utils.js';
|
||||
|
||||
import ExpectConsole from './expect-console.js';
|
||||
import { expectToClickDetailsPageOption, expectToClickModalAction } from './index.js';
|
||||
|
||||
export default class ExpectApiResources extends ExpectConsole {
|
||||
/**
|
||||
* Go to the api resources page and create a new API resource, then assert that the URL matches
|
||||
* the API resource detail page.
|
||||
*
|
||||
* @param {Object} params The parameters for creating the API resource.
|
||||
* @param {string} params.name The name of the API resource to create.
|
||||
* @param {string} params.indicator The indicator of the API resource to create.
|
||||
*/
|
||||
async toCreateApiResource({ name, indicator }: { name: string; indicator: string }) {
|
||||
await this.gotoPage('/api-resources', 'API resources');
|
||||
await this.toClickButton('Create API resource');
|
||||
|
||||
await this.toExpectModal('Start with tutorials');
|
||||
|
||||
// Click bottom button to skip tutorials
|
||||
await this.toClickButton('Continue without tutorial', false);
|
||||
|
||||
await this.toExpectModal('Create API resource');
|
||||
|
||||
await this.toFillForm({
|
||||
name,
|
||||
indicator,
|
||||
});
|
||||
|
||||
await this.toClick(
|
||||
['.ReactModalPortal', `button${cls('primary')}`].join(' '),
|
||||
'Create API resource',
|
||||
false
|
||||
);
|
||||
|
||||
this.toMatchUrl(/\/api-resources\/.+$/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the api resource details page by given resource name and create a new API permission, then assert the permission is created.
|
||||
*
|
||||
* @param {Object} params The parameters for creating the API permission.
|
||||
* @param {string} params.name The name of the API permission to create.
|
||||
* @param {string} params.description The description of the API permission to create.
|
||||
* @param {string} forResourceName The name of the API resource for which the permission is created.
|
||||
*/
|
||||
async toCreateApiResourcePermission(
|
||||
{
|
||||
name,
|
||||
description,
|
||||
}: {
|
||||
name: string;
|
||||
description: string;
|
||||
},
|
||||
forResourceName: string
|
||||
) {
|
||||
await this.gotoPage('/api-resources', 'API resources');
|
||||
await this.toExpectTableHeaders('API name', 'API Identifier');
|
||||
|
||||
await expect(this.page).toClick(['table', 'tbody', 'tr', 'td', `a${cls('title')}`].join(' '), {
|
||||
text: forResourceName,
|
||||
});
|
||||
|
||||
// Expect the API resource details page
|
||||
await expect(this.page).toMatchElement([cls('header'), cls('metadata'), 'div'].join(' '), {
|
||||
text: forResourceName,
|
||||
});
|
||||
|
||||
await this.toClickButton('Create permission', false);
|
||||
|
||||
await this.toExpectModal('Create permission');
|
||||
|
||||
await this.toFillForm({ name, description });
|
||||
|
||||
await this.toClick(
|
||||
['.ReactModalPortal', `button${cls('primary')}`].join(' '),
|
||||
'Create permission',
|
||||
false
|
||||
);
|
||||
|
||||
await this.waitForToast(`The permission ${name} has been successfully created`);
|
||||
|
||||
await this.toExpectTableCell(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the api resource details page by given resource name and delete the API resource.
|
||||
*
|
||||
* @param {string} name The name of the API resource to delete.
|
||||
*/
|
||||
async toDeleteApiResource(name: string) {
|
||||
await this.gotoPage('/api-resources', 'API resources');
|
||||
await this.toExpectTableCell(name);
|
||||
await this.toClickTableCell(name);
|
||||
|
||||
await expectToClickDetailsPageOption(this.page, 'Delete');
|
||||
|
||||
await this.toExpectModal('Reminder');
|
||||
await this.toFill('.ReactModalPortal input', name);
|
||||
|
||||
await expectToClickModalAction(this.page, 'Delete');
|
||||
await this.waitForToast(`The API resource ${name} has been successfully deleted`);
|
||||
}
|
||||
}
|
|
@ -19,7 +19,11 @@ type ExpectConsoleOptions = {
|
|||
tenantId?: string;
|
||||
};
|
||||
|
||||
export type ConsoleTitle = 'Sign-in experience' | 'Organizations';
|
||||
export type ConsoleTitle =
|
||||
| 'Sign-in experience'
|
||||
| 'Organizations'
|
||||
| 'API resources'
|
||||
| 'Organization template';
|
||||
|
||||
export default class ExpectConsole extends ExpectPage {
|
||||
readonly options: Required<ExpectConsoleOptions>;
|
||||
|
@ -59,9 +63,15 @@ export default class ExpectConsole extends ExpectPage {
|
|||
|
||||
/**
|
||||
* Navigate to a specific page in the Console.
|
||||
* If the current page is the target page, it will not navigate.
|
||||
*/
|
||||
async gotoPage(pathname: string, title: ConsoleTitle) {
|
||||
await this.navigateTo(this.buildUrl(path.join(this.options.tenantId, pathname)));
|
||||
const target = this.buildUrl(path.join(this.options.tenantId, pathname));
|
||||
if (this.page.url() === target.href) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.navigateTo(target);
|
||||
await expect(this.page).toMatchElement(
|
||||
[dcls('main'), dcls('container'), dcls('title')].join(' '),
|
||||
{ text: title }
|
||||
|
@ -112,6 +122,19 @@ export default class ExpectConsole extends ExpectPage {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* To click a table cell with the given text.
|
||||
* @param text The text to expect, case-insensitive.
|
||||
* @param shouldNavigate Whether to navigate to the page after clicking the cell.
|
||||
*/
|
||||
async toClickTableCell(text: string, shouldNavigate = true) {
|
||||
await this.toClick(
|
||||
['table', 'tbody', 'tr', 'td'].join(' '),
|
||||
new RegExp(text, 'i'),
|
||||
shouldNavigate
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expect a modal to appear with the given title.
|
||||
*
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { cls } from '#src/utils.js';
|
||||
import { cls, dcls } from '#src/utils.js';
|
||||
|
||||
import ExpectConsole from './expect-console.js';
|
||||
import { selectDropdownMenuItem } from './select-dropdown-menu-item.js';
|
||||
|
||||
export default class ExpectOrganizations extends ExpectConsole {
|
||||
/**
|
||||
|
@ -20,4 +21,156 @@ export default class ExpectOrganizations extends ExpectConsole {
|
|||
await this.toClick(['.ReactModalPortal', `button${cls('primary')}`].join(' '), 'Create', false);
|
||||
this.toMatchUrl(/\/organizations\/.+$/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to he organization template page and create a new organization permission,
|
||||
* then assert then assert the permission is created.
|
||||
*
|
||||
* @param param The parameters for creating the organization permission.
|
||||
* @param param.name The name of the organization permission.
|
||||
* @param param.description The description of the organization permission.
|
||||
*/
|
||||
async toCreateOrganizationPermission({
|
||||
name,
|
||||
description,
|
||||
}: {
|
||||
name: string;
|
||||
description: string;
|
||||
}) {
|
||||
await this.gotoPage('/organization-template', 'Organization template');
|
||||
await this.toClickTab('Organization permissions');
|
||||
await this.toClickButton('Create organization permission', false);
|
||||
|
||||
// Use fill input since no form tag is present in this modal
|
||||
await this.toExpectModal('Create organization permission');
|
||||
await this.toFillInput('name', name);
|
||||
await this.toFillInput('description', description);
|
||||
await this.toClickButton('Create permission', false);
|
||||
|
||||
await this.toExpectTableCell(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the organization template page and create a new organization role,
|
||||
* then skip the permission assignment and assert that the URL matches
|
||||
* the organization detail page.
|
||||
*
|
||||
* @param param The parameters for creating the organization role.
|
||||
* @param param.name The name of the organization role.
|
||||
* @param param.description The description of the organization role.
|
||||
*/
|
||||
async toCreateOrganizationRole({ name, description }: { name: string; description: string }) {
|
||||
await this.gotoPage('/organization-template/organization-roles', 'Organization template');
|
||||
await this.toClickTab('Organization roles');
|
||||
await this.toClickButton('Create organization role', false);
|
||||
|
||||
// Use fill input since no form tag is present in this modal
|
||||
await this.toExpectModal('Create organization role');
|
||||
await this.toFillInput('name', name);
|
||||
await this.toFillInput('description', description);
|
||||
await this.toClickButton('Create role', false);
|
||||
|
||||
// Skip permission assignment
|
||||
await this.toExpectModal('Assign permissions');
|
||||
await this.toClickButton('Discard');
|
||||
|
||||
this.toMatchUrl(/\/organization-template\/organization-roles\/.+$/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the organization role details page, assign organization permissions and API permissions for an organization role,
|
||||
* then assert the permissions are assigned.
|
||||
*
|
||||
* @param param The parameters for assigning permissions for an organization role.
|
||||
* @param param.organizationPermission The name of the organization permission to assign.
|
||||
* @param param.apiPermission The API permission to assign.
|
||||
* @param param.forOrganizationRole The organization role to assign the permissions.
|
||||
*/
|
||||
async toAssignPermissionsForOrganizationRole({
|
||||
organizationPermission,
|
||||
apiPermission: { resource: apiResource, permission: apiPermission },
|
||||
forOrganizationRole,
|
||||
}: {
|
||||
organizationPermission: string;
|
||||
apiPermission: {
|
||||
resource: string;
|
||||
permission: string;
|
||||
};
|
||||
forOrganizationRole: string;
|
||||
}) {
|
||||
await this.navigateToOrganizationRoleDetailsPage(forOrganizationRole);
|
||||
|
||||
await this.toClickButton('Assign permissions', false);
|
||||
await this.toExpectModal('Assign permissions');
|
||||
// Select organization permission
|
||||
await this.toClickTab('Organization permissions');
|
||||
await this.toClick(`div[role=button]`, organizationPermission, false);
|
||||
|
||||
// Select API permission
|
||||
await this.toClickTab('API permissions');
|
||||
await this.toClick(`div[role=button]`, apiResource, false);
|
||||
await this.toClick(`div[role=button]`, apiPermission, false);
|
||||
|
||||
await this.toClickButton('Save', false);
|
||||
|
||||
await this.toExpectTableCell(organizationPermission);
|
||||
await this.toExpectTableCell(apiPermission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the organization role details page and delete an organization role,
|
||||
* then assert that the URL matches the organization roles page.
|
||||
*
|
||||
* @param name The name of the organization role to delete.
|
||||
*/
|
||||
async toDeleteOrganizationRole(name: string) {
|
||||
await this.navigateToOrganizationRoleDetailsPage(name);
|
||||
|
||||
// Open the dropdown menu
|
||||
await this.toClick([dcls('header'), `button${cls('withIcon')}`].join(' '), undefined, false);
|
||||
await this.toClick(`${dcls('danger')}[role=menuitem]`, 'Delete', false);
|
||||
await this.toExpectModal('Reminder');
|
||||
await this.toClick(['.ReactModalPortal', `button${cls('danger')}`].join(' '), 'Delete');
|
||||
await this.waitForToast(`Organization role ${name} was successfully deleted.`, 'success');
|
||||
this.toMatchUrl(/\/organization-template\/organization-roles$/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the organization permissions page and delete an organization permission,
|
||||
*
|
||||
* @param name The name of the organization permission to delete.
|
||||
*/
|
||||
async toDeleteOrganizationPermission(name: string) {
|
||||
await this.gotoPage('/organization-template', 'Organization template');
|
||||
await this.toClickTab('Organization permissions');
|
||||
await this.toExpectTableCell(name);
|
||||
|
||||
// Open the dropdown menu from the table row
|
||||
const permissionRow = await expect(this.page).toMatchElement('table tbody tr:has(td div)', {
|
||||
text: name,
|
||||
});
|
||||
|
||||
// Click the action button from the permission row
|
||||
await expect(permissionRow).toClick('td:last-of-type button');
|
||||
|
||||
await selectDropdownMenuItem(this.page, 'div[role=menuitem]', 'Delete permission');
|
||||
|
||||
await this.toExpectModal('Reminder');
|
||||
await this.toClick(['.ReactModalPortal', `button${cls('danger')}`].join(' '), 'Delete', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the organization role details page by given role name.
|
||||
*
|
||||
* @param name The name of the organization role.
|
||||
*/
|
||||
private async navigateToOrganizationRoleDetailsPage(name: string) {
|
||||
await this.gotoPage('/organization-template', 'Organization template');
|
||||
await this.toClickTab('Organization roles');
|
||||
await this.toExpectTableCell(name);
|
||||
await this.toClickTableCell(name);
|
||||
|
||||
// Assert in details page
|
||||
this.toMatchUrl(/\/organization-template\/organization-roles\/.+$/);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue