0
Fork 0
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:
Xiao Yijun 2024-04-18 14:33:10 +08:00 committed by GitHub
parent e1d5b8a72a
commit 45157873a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 374 additions and 3 deletions

View file

@ -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);
});
});

View file

@ -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`);
}
}

View file

@ -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.
*

View file

@ -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\/.+$/);
}
}