From 837a73e8746d7cb954bba15de2a230d48d59d82b Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Thu, 23 Feb 2023 15:27:41 +0100 Subject: [PATCH] SONAR-18429 Migrate permission templates app tests to RTL --- .../js/api/mocks/PermissionsServiceMock.ts | 216 ++++--- .../sonar-web/src/main/js/api/permissions.ts | 19 +- .../components/ActionsCell.tsx | 8 +- .../components/DeleteForm.tsx | 8 +- .../permission-templates/components/Form.tsx | 8 +- .../components/Header.tsx | 3 +- .../permission-templates/components/Home.tsx | 14 +- .../components/PermissionTemplatesApp.tsx | 47 +- .../components/Template.tsx | 49 +- .../components/TemplateHeader.tsx | 5 +- .../components/__tests__/ActionsCell-test.tsx | 56 -- .../components/__tests__/Defaults-test.tsx | 43 -- .../components/__tests__/Form-test.tsx | 30 - .../components/__tests__/ListItem-test.tsx | 43 -- .../components/__tests__/NameCell-test.tsx | 40 -- .../__tests__/PermissionTemplatesApp-it.tsx | 525 ++++++++++++++++-- .../__snapshots__/Defaults-test.tsx.snap | 21 - .../__snapshots__/Form-test.tsx.snap | 12 - .../__snapshots__/ListItem-test.tsx.snap | 37 -- .../__snapshots__/NameCell-test.tsx.snap | 39 -- .../PermissionTemplatesApp-it.tsx.snap | 25 + .../__tests__/PermissionsProject-it.tsx | 4 +- .../src/main/js/helpers/mocks/permissions.ts | 19 - .../resources/org/sonar/l10n/core.properties | 1 + 24 files changed, 737 insertions(+), 535 deletions(-) delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ActionsCell-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Defaults-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Form-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/ListItem-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/NameCell-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/Defaults-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/Form-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/ListItem-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/NameCell-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/PermissionTemplatesApp-it.tsx.snap diff --git a/server/sonar-web/src/main/js/api/mocks/PermissionsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/PermissionsServiceMock.ts index c8edbe64903..4d7b322fd66 100644 --- a/server/sonar-web/src/main/js/api/mocks/PermissionsServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/PermissionsServiceMock.ts @@ -22,10 +22,10 @@ import { mockPermission, mockPermissionGroup, mockPermissionTemplate, + mockPermissionTemplateGroup, mockPermissionUser, - mockTemplateGroup, - mockTemplateUser, } from '../../helpers/mocks/permissions'; +import { PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE } from '../../helpers/permissions'; import { ComponentQualifier, Visibility } from '../../types/component'; import { Permissions } from '../../types/permissions'; import { Permission, PermissionGroup, PermissionTemplate, PermissionUser } from '../../types/types'; @@ -35,6 +35,8 @@ import { applyTemplateToProject, bulkApplyTemplate, changeProjectVisibility, + createPermissionTemplate, + deletePermissionTemplate, getGlobalPermissionsGroups, getGlobalPermissionsUsers, getPermissionsGroupsForComponent, @@ -51,56 +53,43 @@ import { revokePermissionFromUser, revokeTemplatePermissionFromGroup, revokeTemplatePermissionFromUser, + setDefaultPermissionTemplate, + updatePermissionTemplate, } from '../permissions'; const MAX_PROJECTS_TO_APPLY_PERMISSION_TEMPLATE = 10; -const defaultPermissionTemplates: PermissionTemplate[] = [ - mockPermissionTemplate(), - mockPermissionTemplate({ - id: 'template2', - name: 'Permission Template 2', - }), -]; - -const templateUsers = [ - mockTemplateUser(), - mockTemplateUser({ +const defaultUsers = [ + mockPermissionUser(), + mockPermissionUser({ login: 'gooduser1', name: 'John', - permissions: ['issueadmin', 'securityhotspotadmin', 'user'], + permissions: [Permissions.IssueAdmin, Permissions.SecurityHotspotAdmin, Permissions.Browse], }), - mockTemplateUser({ + mockPermissionUser({ login: 'gooduser2', name: 'Alexa', - permissions: ['issueadmin', 'user'], + permissions: [Permissions.IssueAdmin, Permissions.Browse], }), - mockTemplateUser({ + mockPermissionUser({ name: 'Siri', login: 'gooduser3', }), - mockTemplateUser({ + mockPermissionUser({ login: 'gooduser4', name: 'Cool', - permissions: ['user'], + permissions: [Permissions.Browse], }), - mockTemplateUser({ + mockPermissionUser({ name: 'White', login: 'baduser1', }), - mockTemplateUser({ + mockPermissionUser({ name: 'Green', login: 'baduser2', }), ]; -const templateGroups = [ - mockTemplateGroup(), - mockTemplateGroup({ id: 'admins', name: 'admins', permissions: [] }), -]; - -const defaultUsers = [mockPermissionUser()]; - const defaultGroups = [ mockPermissionGroup({ name: 'sonar-users', permissions: [Permissions.Browse] }), mockPermissionGroup({ @@ -110,6 +99,39 @@ const defaultGroups = [ mockPermissionGroup({ name: 'sonar-losers', permissions: [] }), ]; +const defaultTemplates: PermissionTemplate[] = [ + mockPermissionTemplate({ + id: 'template1', + name: 'Permission Template 1', + description: 'This is permission template 1', + defaultFor: [ + ComponentQualifier.Project, + ComponentQualifier.Application, + ComponentQualifier.Portfolio, + ], + permissions: PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE.map((key) => + mockPermissionTemplateGroup({ + key, + groupsCount: defaultGroups.filter((g) => g.permissions.includes(key)).length, + usersCount: defaultUsers.filter((g) => g.permissions.includes(key)).length, + withProjectCreator: false, + }) + ), + }), + mockPermissionTemplate({ + id: 'template2', + name: 'Permission Template 2', + permissions: PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE.map((key) => + mockPermissionTemplateGroup({ + key, + groupsCount: 0, + usersCount: 0, + withProjectCreator: [Permissions.Browse, Permissions.CodeViewer].includes(key), + }) + ), + }), +]; + const PAGE_SIZE = 5; const MIN_QUERY_LENGTH = 3; const DEFAULT_PAGE = 1; @@ -119,28 +141,19 @@ jest.mock('../permissions'); export default class PermissionsServiceMock { #permissionTemplates: PermissionTemplate[] = []; #permissions: Permission[]; - #defaultTemplates: Array<{ templateId: string; qualifier: string }>; + #defaultTemplates: Array<{ templateId: string; qualifier: string }> = []; #groups: PermissionGroup[]; #users: PermissionUser[]; #isAllowedToChangePermissions = true; constructor() { - this.#permissionTemplates = cloneDeep(defaultPermissionTemplates); - this.#defaultTemplates = [ - ComponentQualifier.Project, - ComponentQualifier.Application, - ComponentQualifier.Portfolio, - ].map((qualifier) => ({ templateId: this.#permissionTemplates[0].id, qualifier })); - this.#permissions = [ - Permissions.Admin, - Permissions.CodeViewer, - Permissions.IssueAdmin, - Permissions.SecurityHotspotAdmin, - Permissions.Scan, - Permissions.Browse, - ].map((key) => mockPermission({ key, name: key })); + this.#permissionTemplates = cloneDeep(defaultTemplates); + this.#permissions = PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE.map((key) => + mockPermission({ key, name: key }) + ); this.#groups = cloneDeep(defaultGroups); this.#users = cloneDeep(defaultUsers); + this.updateDefaults(); jest.mocked(getPermissionTemplates).mockImplementation(this.handleGetPermissionTemplates); jest.mocked(bulkApplyTemplate).mockImplementation(this.handleBulkApplyTemplate); @@ -157,6 +170,12 @@ export default class PermissionsServiceMock { jest.mocked(revokeTemplatePermissionFromGroup).mockImplementation(this.handlePermissionChange); jest.mocked(grantTemplatePermissionToUser).mockImplementation(this.handlePermissionChange); jest.mocked(revokeTemplatePermissionFromUser).mockImplementation(this.handlePermissionChange); + jest.mocked(createPermissionTemplate).mockImplementation(this.handleCreatePermissionTemplate); + jest.mocked(updatePermissionTemplate).mockImplementation(this.handleUpdatePermissionTemplate); + jest.mocked(deletePermissionTemplate).mockImplementation(this.handleDeletePermissionTemplate); + jest + .mocked(setDefaultPermissionTemplate) + .mockImplementation(this.handleSetDefaultPermissionTemplate); jest.mocked(changeProjectVisibility).mockImplementation(this.handleChangeProjectVisibility); jest.mocked(getGlobalPermissionsUsers).mockImplementation(this.handleGetPermissionUsers); jest.mocked(getGlobalPermissionsGroups).mockImplementation(this.handleGetPermissionGroups); @@ -198,44 +217,24 @@ export default class PermissionsServiceMock { return Promise.resolve(); }; - handleGetPermissionTemplateUsers = (data: { q?: string | null; p?: number; ps?: number }) => { - const { ps = PAGE_SIZE, p = DEFAULT_PAGE, q } = data; - - const users = - q && q.length >= MIN_QUERY_LENGTH - ? templateUsers.filter((user) => - [user.login, user.name].some((key) => key.toLowerCase().includes(q.toLowerCase())) - ) - : templateUsers; - - const usersChunks = chunk(users, ps); - - return this.reply({ - paging: { pageSize: ps, total: users.length, pageIndex: p }, - users: usersChunks[p - 1] ?? [], - }); + handleGetPermissionTemplateUsers = (data: { + templateId: string; + q?: string; + permission?: string; + p?: number; + ps?: number; + }) => { + return this.handleGetPermissionUsers(data); }; handleGetPermissionTemplateGroups = (data: { templateId: string; - q?: string | null; + q?: string; permission?: string; p?: number; ps?: number; }) => { - const { ps = PAGE_SIZE, p = DEFAULT_PAGE, q } = data; - - const groups = - q && q.length >= MIN_QUERY_LENGTH - ? templateGroups.filter((group) => group.name.toLowerCase().includes(q.toLowerCase())) - : templateGroups; - - const groupsChunks = chunk(groups, ps); - - return this.reply({ - paging: { pageSize: ps, total: groups.length, pageIndex: p }, - groups: groupsChunks[p - 1] ?? [], - }); + return this.handleGetPermissionGroups(data); }; handleChangeProjectVisibility = (_project: string, _visibility: Visibility) => { @@ -386,6 +385,58 @@ export default class PermissionsServiceMock { return this.#isAllowedToChangePermissions ? Promise.resolve() : Promise.reject(); }; + handleCreatePermissionTemplate = (data: { + name: string; + description?: string; + projectKeyPattern?: string; + }) => { + const newTemplate = mockPermissionTemplate({ + id: `template-${this.#permissionTemplates.length + 1}`, + ...data, + }); + this.#permissionTemplates.push(newTemplate); + return this.reply({ permissionTemplate: newTemplate }); + }; + + handleUpdatePermissionTemplate = (data: { + id: string; + description?: string; + name?: string; + projectKeyPattern?: string; + }) => { + const { id } = data; + const template = this.#permissionTemplates.find((t) => t.id === id); + if (template === undefined) { + throw new Error(`Couldn't find template with id ${id}`); + } + Object.assign(template, data); + + return this.reply(undefined); + }; + + handleDeletePermissionTemplate = (data: { templateId?: string; templateName?: string }) => { + const { templateId } = data; + this.#permissionTemplates = this.#permissionTemplates.filter((t) => t.id !== templateId); + return this.reply(undefined); + }; + + handleSetDefaultPermissionTemplate = (templateId: string, qualifier: ComponentQualifier) => { + this.#permissionTemplates = this.#permissionTemplates.map((t) => ({ + ...t, + defaultFor: t.defaultFor.filter((q) => q !== qualifier), + })); + + const template = this.#permissionTemplates.find((t) => t.id === templateId); + if (template === undefined) { + throw new Error(`Couldn't find template with id ${templateId}`); + } + template.defaultFor = uniq([...template.defaultFor, qualifier]); + + this.updateDefaults(); + + return this.reply(undefined); + }; + setIsAllowedToChangePermissions = (val: boolean) => { this.#isAllowedToChangePermissions = val; }; @@ -398,8 +449,25 @@ export default class PermissionsServiceMock { this.#users = users; }; + getTemplates = () => { + return this.#permissionTemplates; + }; + + updateDefaults = () => { + this.#defaultTemplates = [ + ComponentQualifier.Project, + ComponentQualifier.Application, + ComponentQualifier.Portfolio, + ].map((qualifier) => ({ + templateId: + this.#permissionTemplates.find((t) => t.defaultFor.includes(qualifier))?.id ?? + this.#permissionTemplates[0].id, + qualifier, + })); + }; + reset = () => { - this.#permissionTemplates = cloneDeep(defaultPermissionTemplates); + this.#permissionTemplates = cloneDeep(defaultTemplates); this.#groups = cloneDeep(defaultGroups); this.#users = cloneDeep(defaultUsers); this.setIsAllowedToChangePermissions(true); diff --git a/server/sonar-web/src/main/js/api/permissions.ts b/server/sonar-web/src/main/js/api/permissions.ts index 8e65bd6ead7..dfde7efcc22 100644 --- a/server/sonar-web/src/main/js/api/permissions.ts +++ b/server/sonar-web/src/main/js/api/permissions.ts @@ -74,15 +74,24 @@ export function getPermissionTemplates(): Promise }> { return postJSON('/api/permissions/create_template', data); } -export function updatePermissionTemplate(data: RequestData): Promise { +export function updatePermissionTemplate(data: { + id: string; + description?: string; + name?: string; + projectKeyPattern?: string; +}): Promise { return post('/api/permissions/update_template', data); } -export function deletePermissionTemplate(data: RequestData) { +export function deletePermissionTemplate(data: { templateId?: string; templateName?: string }) { return post('/api/permissions/delete_template', data).catch(throwGlobalError); } @@ -188,7 +197,7 @@ export function getGlobalPermissionsGroups(data: { export function getPermissionTemplateUsers(data: { templateId: string; - q?: string | null; + q?: string; permission?: string; p?: number; ps?: number; @@ -201,7 +210,7 @@ export function getPermissionTemplateUsers(data: { export function getPermissionTemplateGroups(data: { templateId: string; - q?: string | null; + q?: string; permission?: string; p?: number; ps?: number; diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx index 3423924685f..17bc11e53ea 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.tsx @@ -27,7 +27,7 @@ import { import ActionsDropdown, { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown'; import { Router, withRouter } from '../../../components/hoc/withRouter'; import QualifierIcon from '../../../components/icons/QualifierIcon'; -import { translate } from '../../../helpers/l10n'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; import { queryToSearch } from '../../../helpers/urls'; import { PermissionTemplate } from '../../../types/types'; import { PERMISSION_TEMPLATES_PATH } from '../utils'; @@ -47,7 +47,7 @@ interface State { updateModal: boolean; } -export class ActionsCell extends React.PureComponent { +class ActionsCell extends React.PureComponent { mounted = false; state: State = { deleteForm: false, updateModal: false }; @@ -158,7 +158,9 @@ export class ActionsCell extends React.PureComponent { return ( <> - + {this.renderSetDefaultsControl()} {!this.props.fromDetails && ( diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/DeleteForm.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/DeleteForm.tsx index 1429bed942e..fbf1ffe6ca5 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/DeleteForm.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/DeleteForm.tsx @@ -37,9 +37,9 @@ export default function DeleteForm({ onClose, onSubmit, permissionTemplate: t }: {({ onCloseClick, onFormSubmit, submitting }) => (
-
+

{header}

-
+
{translateWithParameters( @@ -48,7 +48,7 @@ export default function DeleteForm({ onClose, onSubmit, permissionTemplate: t }: )}
-
+
{translate('delete')} @@ -56,7 +56,7 @@ export default function DeleteForm({ onClose, onSubmit, permissionTemplate: t }: {translate('cancel')} -
+
)}
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx index f09bb51d361..6ad3b0bc7c3 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx @@ -88,9 +88,9 @@ export default class Form extends React.PureComponent { > {({ onCloseClick, onFormSubmit, submitting }) => (
-
+

{this.props.header}

-
+
@@ -140,7 +140,7 @@ export default class Form extends React.PureComponent {
-
+
{this.props.confirmButtonText} @@ -152,7 +152,7 @@ export default class Form extends React.PureComponent { > {translate('cancel')} -
+
)} diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx index ffae2347077..86da435b83d 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import { createPermissionTemplate } from '../../../api/permissions'; import { Button } from '../../../components/controls/buttons'; import { Router, withRouter } from '../../../components/hoc/withRouter'; +import DeferredSpinner from '../../../components/ui/DeferredSpinner'; import { translate } from '../../../helpers/l10n'; import { PERMISSION_TEMPLATES_PATH } from '../utils'; import Form from './Form'; @@ -77,7 +78,7 @@ class Header extends React.PureComponent {

{translate('permission_templates.page')}

- {!this.props.ready && } +
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Home.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/Home.tsx index f7bbc5bbd8a..f7591ffbd9c 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/Home.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Home.tsx @@ -39,12 +39,14 @@ export default function Home(props: Props) {
- +
+ +
); } diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/PermissionTemplatesApp.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/PermissionTemplatesApp.tsx index 0a613050b6f..0806b4325c0 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/PermissionTemplatesApp.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/PermissionTemplatesApp.tsx @@ -42,7 +42,7 @@ interface State { permissionTemplates: PermissionTemplate[]; } -export class PermissionTemplatesApp extends React.PureComponent { +class PermissionTemplatesApp extends React.PureComponent { mounted = false; state: State = { ready: false, @@ -52,16 +52,15 @@ export class PermissionTemplatesApp extends React.PureComponent { componentDidMount() { this.mounted = true; - this.requestPermissions(); + this.handleRefresh(); } componentWillUnmount() { this.mounted = false; } - requestPermissions = async () => { + handleRefresh = async () => { const { permissions, defaultTemplates, permissionTemplates } = await getPermissionTemplates(); - if (this.mounted) { const sortedPerm = sortPermissions(permissions); const permissionTemplatesMerged = mergeDefaultsToTemplates( @@ -77,46 +76,46 @@ export class PermissionTemplatesApp extends React.PureComponent { }; renderTemplate(id: string) { - if (!this.state.ready) { + const { permissionTemplates, ready } = this.state; + if (!ready) { return null; } - const template = this.state.permissionTemplates.find((t) => t.id === id); + const template = permissionTemplates.find((t) => t.id === id); if (!template) { return null; } return (