1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-05 17:53:12 +02:00

fix: make necessary changes for dropping the id col

This commit is contained in:
Nuno Góis 2023-11-22 18:02:33 +00:00
parent e5d57bd873
commit f780391b44
No known key found for this signature in database
GPG Key ID: 71ECC689F1091765
9 changed files with 95 additions and 283 deletions

View File

@ -114,7 +114,7 @@ export const RoleDescription = ({
</StyledDescriptionHeader>
<StyledPermissionsList>
{permissions.map((permission) => (
<li key={permission.id}>
<li key={permission.name}>
{permission.displayName}
</li>
))}

View File

@ -10,7 +10,6 @@ export type PermissionType =
| typeof ENVIRONMENT_PERMISSION_TYPE;
export interface IPermission {
id: number;
name: string;
displayName: string;
type: PermissionType;

View File

@ -1,110 +0,0 @@
import dbInit from '../../test/e2e/helpers/database-init';
import getLogger from '../../test/fixtures/no-logger';
import { PermissionRef } from 'lib/services/access-service';
import { AccessStore } from './access-store';
let db;
beforeAll(async () => {
db = await dbInit('access_store_serial', getLogger);
});
afterAll(async () => {
if (db) {
await db.destroy();
}
});
// Helper function to make the test cases more readable
const args = (permissions: PermissionRef[], expectations?: PermissionRef[]) => {
if (expectations) {
return [permissions, expectations];
} else {
return [permissions];
}
};
test('resolvePermissions returns empty list if undefined', async () => {
const access = db.stores.accessStore as AccessStore;
const result = await access.resolvePermissions(
undefined as unknown as PermissionRef[],
);
expect(result).toStrictEqual([]);
});
test('resolvePermissions returns empty list if empty list', async () => {
const access = db.stores.accessStore as AccessStore;
const result = await access.resolvePermissions([] as PermissionRef[]);
expect(result).toStrictEqual([]);
});
test.each([
args([{ id: 1 }]),
args([{ id: 4, environment: 'development' }]),
args([{ id: 4, name: 'should keep the id' }]),
args([
{ id: 1, environment: 'development' },
{ id: 2, name: 'ignore this name' },
]),
])(
'resolvePermissions with permission ids (%o) returns the list unmodified',
async (permissions) => {
const access = db.stores.accessStore as AccessStore;
const result = await access.resolvePermissions(permissions);
expect(result).toStrictEqual(permissions);
},
);
test.each([
args(
[{ name: 'CREATE_CONTEXT_FIELD' }],
[{ id: 18, name: 'CREATE_CONTEXT_FIELD' }],
),
args(
[{ name: 'CREATE_FEATURE', environment: 'development' }],
[{ id: 2, name: 'CREATE_FEATURE', environment: 'development' }],
),
args(
[
{ name: 'CREATE_CONTEXT_FIELD' },
{ name: 'CREATE_FEATURE', environment: 'development' },
],
[
{ id: 18, name: 'CREATE_CONTEXT_FIELD' },
{ id: 2, name: 'CREATE_FEATURE', environment: 'development' },
],
),
])(
'resolvePermissions with permission names (%o) will inject the ids',
async (permissions, expected) => {
const access = db.stores.accessStore as AccessStore;
const result = await access.resolvePermissions(permissions);
expect(result).toStrictEqual(expected);
},
);
test.each([
args(
[
{ name: 'CREATE_CONTEXT_FIELD' },
{ id: 3 },
{ name: 'CREATE_FEATURE', environment: 'development' },
{ id: 15, environment: 'development' },
{ name: 'UPDATE_FEATURE', environment: 'development' },
],
[
{ id: 18, name: 'CREATE_CONTEXT_FIELD' },
{ id: 3 },
{ id: 2, name: 'CREATE_FEATURE', environment: 'development' },
{ id: 15, environment: 'development' },
{ id: 7, name: 'UPDATE_FEATURE', environment: 'development' },
],
),
])(
'resolvePermissions mixed ids and names (%o) will inject the ids where they are missing',
async (permissions, expected) => {
const access = db.stores.accessStore as AccessStore;
const result = await access.resolvePermissions(permissions);
expect(result).toStrictEqual(expected);
},
);

View File

@ -20,11 +20,7 @@ import {
ROOT_PERMISSION_TYPE,
} from '../util/constants';
import { Db } from './db';
import {
IdPermissionRef,
NamePermissionRef,
PermissionRef,
} from 'lib/services/access-service';
import { PermissionRef } from 'lib/services/access-service';
import { inTransaction } from './transaction';
const T = {
@ -42,7 +38,6 @@ const T = {
};
interface IPermissionRow {
id: number;
permission: string;
display_name: string;
environment?: string;
@ -51,12 +46,6 @@ interface IPermissionRow {
role_id: number;
}
type ResolvedPermission = {
id: number;
name?: string;
environment?: string;
};
export class AccessStore implements IAccessStore {
private logger: Logger;
@ -74,75 +63,6 @@ export class AccessStore implements IAccessStore {
});
}
private permissionHasId = (permission: PermissionRef): boolean => {
return (permission as IdPermissionRef).id !== undefined;
};
private permissionNamesToIds = async (
permissions: NamePermissionRef[],
): Promise<ResolvedPermission[]> => {
const permissionNames = (permissions ?? [])
.filter((p) => p.name !== undefined)
.map((p) => p.name);
if (permissionNames.length === 0) {
return [];
}
const stopTimer = this.timer('permissionNamesToIds');
const rows = await this.db
.select('id', 'permission')
.from(T.PERMISSIONS)
.whereIn('permission', permissionNames);
const rowByPermissionName = rows.reduce((acc, row) => {
acc[row.permission] = row;
return acc;
}, {} as Map<string, IPermissionRow>);
const permissionsWithIds = permissions.map((permission) => ({
id: rowByPermissionName[permission.name].id,
...permission,
}));
stopTimer();
return permissionsWithIds;
};
resolvePermissions = async (
permissions: PermissionRef[],
): Promise<ResolvedPermission[]> => {
if (permissions === undefined || permissions.length === 0) {
return [];
}
// permissions without ids (just names)
const permissionsWithoutIds = permissions.filter(
(p) => !this.permissionHasId(p),
) as NamePermissionRef[];
const idPermissionsFromNamed = await this.permissionNamesToIds(
permissionsWithoutIds,
);
if (permissionsWithoutIds.length === permissions.length) {
// all named permissions without ids
return idPermissionsFromNamed;
} else if (permissionsWithoutIds.length === 0) {
// all permissions have ids
return permissions as ResolvedPermission[];
}
// some permissions have ids, some don't (should not happen!)
return permissions.map((permission) => {
if (this.permissionHasId(permission)) {
return permission as ResolvedPermission;
} else {
return idPermissionsFromNamed.find(
(p) => p.name === (permission as NamePermissionRef).name,
)!;
}
});
};
async delete(key: number): Promise<void> {
await this.db(T.ROLES).where({ id: key }).del();
}
@ -182,7 +102,7 @@ export class AccessStore implements IAccessStore {
async getAvailablePermissions(): Promise<IPermission[]> {
const rows = await this.db
.select(['id', 'permission', 'type', 'display_name'])
.select(['permission', 'type', 'display_name'])
.where('type', 'project')
.orWhere('type', 'environment')
.orWhere('type', 'root')
@ -192,7 +112,6 @@ export class AccessStore implements IAccessStore {
mapPermission(permission: IPermissionRow): IPermission {
return {
id: permission.id,
name: permission.permission,
displayName: permission.display_name,
type: permission.type,
@ -211,7 +130,7 @@ export class AccessStore implements IAccessStore {
)
.from<IPermissionRow>(`${T.ROLE_PERMISSION} AS rp`)
.join(`${T.ROLE_USER} AS ur`, 'ur.role_id', 'rp.role_id')
.join(`${T.PERMISSIONS} AS p`, 'p.id', 'rp.permission_id')
.join(`${T.PERMISSIONS} AS p`, 'p.permission', 'rp.permission')
.where('ur.user_id', '=', userId);
userPermissionQuery = userPermissionQuery.union((db) => {
@ -226,7 +145,7 @@ export class AccessStore implements IAccessStore {
.join(`${T.GROUPS} AS g`, 'g.id', 'gu.group_id')
.join(`${T.GROUP_ROLE} AS gr`, 'gu.group_id', 'gr.group_id')
.join(`${T.ROLE_PERMISSION} AS rp`, 'rp.role_id', 'gr.role_id')
.join(`${T.PERMISSIONS} AS p`, 'p.id', 'rp.permission_id')
.join(`${T.PERMISSIONS} AS p`, 'p.permission', 'rp.permission')
.andWhere('gu.user_id', '=', userId);
});
@ -245,7 +164,7 @@ export class AccessStore implements IAccessStore {
'rp.role_id',
'g.root_role_id',
)
.join(`${T.PERMISSIONS} as p`, 'p.id', 'rp.permission_id')
.join(`${T.PERMISSIONS} as p`, 'p.permission', 'rp.permission')
.whereNotNull('g.root_role_id')
.andWhere('gu.user_id', '=', userId);
});
@ -279,19 +198,17 @@ export class AccessStore implements IAccessStore {
const stopTimer = this.timer('getPermissionsForRole');
const rows = await this.db
.select(
'p.id',
'p.permission',
'rp.environment',
'p.display_name',
'p.type',
)
.from<IPermission>(`${T.ROLE_PERMISSION} as rp`)
.join(`${T.PERMISSIONS} as p`, 'p.id', 'rp.permission_id')
.join(`${T.PERMISSIONS} as p`, 'p.permission', 'rp.permission')
.where('rp.role_id', '=', roleId);
stopTimer();
return rows.map((permission) => {
return {
id: permission.id,
name: permission.permission,
environment: permission.environment,
displayName: permission.display_name,
@ -304,12 +221,12 @@ export class AccessStore implements IAccessStore {
role_id: number,
permissions: PermissionRef[],
): Promise<void> {
const resolvedPermission = await this.resolvePermissions(permissions);
const resolvedPermissions = permissions || [];
const rows = resolvedPermission.map((permission) => {
const rows = resolvedPermissions.map((permission) => {
return {
role_id,
permission_id: permission.id,
permission: permission.name,
environment: permission.environment,
};
});
@ -797,14 +714,11 @@ export class AccessStore implements IAccessStore {
}
});
// no need to pass down the environment in this particular case because it'll be overriden
const permissionsWithIds = await this.resolvePermissions(
permissionsAsRefs,
);
const newRoles = permissionsWithIds.map((p) => ({
const newRoles = permissionsAsRefs.map((p) => ({
role_id,
environment,
permission_id: p.id,
permission: p.name,
}));
return this.db.batchInsert(T.ROLE_PERMISSION, newRoles);
@ -815,17 +729,10 @@ export class AccessStore implements IAccessStore {
permission: string,
environment?: string,
): Promise<void> {
const rows = await this.db
.select('id as permissionId')
.from<number>(T.PERMISSIONS)
.where('permission', permission);
const permissionId = rows[0].permissionId;
return this.db(T.ROLE_PERMISSION)
.where({
role_id,
permission_id: permissionId,
permission,
environment,
})
.delete();
@ -845,8 +752,8 @@ export class AccessStore implements IAccessStore {
): Promise<void> {
return this.db.raw(
`insert into role_permission
(role_id, permission_id, environment)
(select role_id, permission_id, ?
(role_id, permission, environment)
(select role_id, permission, ?
from ${T.ROLE_PERMISSION} where environment = ?)`,
[destinationEnvironment, sourceEnvironment],
);

View File

@ -52,10 +52,7 @@ const PROJECT_ADMIN = [
permissions.DELETE_FEATURE,
];
/** @deprecated prefer to use NamePermissionRef */
export type IdPermissionRef = Pick<IPermission, 'id' | 'environment'>;
export type NamePermissionRef = Pick<IPermission, 'name' | 'environment'>;
export type PermissionRef = IdPermissionRef | NamePermissionRef;
export type PermissionRef = Pick<IPermission, 'name' | 'environment'>;
interface IRoleCreation {
name: string;

View File

@ -331,7 +331,6 @@ export interface IAvailablePermissions {
}
export interface IPermission {
id: number;
name: string;
displayName: string;
type: string;

View File

@ -5,24 +5,24 @@ exports.up = function (db, cb) {
ALTER TABLE permissions
ADD CONSTRAINT permission_unique UNIQUE (permission);
-- STEP 2: Add the primary key constraint
ALTER TABLE permissions
ADD PRIMARY KEY (permission);
-- STEP 3: Add a new column for the new foreign key
-- STEP 2: Add a new column for the new foreign key
ALTER TABLE role_permission
ADD COLUMN permission text;
-- STEP 4: Populate the new column with corresponding values from 'permissions'
-- STEP 3: Populate the new column with corresponding values from 'permissions'
UPDATE role_permission rp
SET permission = p.permission
FROM permissions p
WHERE rp.permission_id = p.id;
-- STEP 5: Drop the 'id' column
-- STEP 4: Drop the 'id' column
ALTER TABLE permissions
DROP COLUMN id;
-- STEP 5: Add the primary key constraint
ALTER TABLE permissions
ADD PRIMARY KEY (permission);
-- STEP 6: Drop the old foreign key column
ALTER TABLE role_permission
DROP COLUMN permission_id;
@ -30,6 +30,61 @@ DROP COLUMN permission_id;
-- STEP 7: Add the new foreign key constraint
ALTER TABLE role_permission
ADD CONSTRAINT fk_role_permission_permission FOREIGN KEY (permission) REFERENCES permissions(permission) ON DELETE CASCADE;
-- STEP 8: Ensure that all the expected permissions exist
INSERT INTO permissions (permission, display_name, type)
SELECT * FROM (VALUES
('ADMIN', 'Admin', 'root'),
('CREATE_FEATURE', 'Create feature toggles', 'project'),
('CREATE_STRATEGY', 'Create activation strategies', 'root'),
('CREATE_ADDON', 'Create addons', 'root'),
('DELETE_ADDON', 'Delete addons', 'root'),
('UPDATE_ADDON', 'Update addons', 'root'),
('UPDATE_FEATURE', 'Update feature toggles', 'project'),
('DELETE_FEATURE', 'Delete feature toggles', 'project'),
('UPDATE_APPLICATION', 'Update applications', 'root'),
('UPDATE_TAG_TYPE', 'Update tag types', 'root'),
('DELETE_TAG_TYPE', 'Delete tag types', 'root'),
('CREATE_PROJECT', 'Create projects', 'root'),
('UPDATE_PROJECT', 'Update project', 'project'),
('DELETE_PROJECT', 'Delete project', 'project'),
('UPDATE_STRATEGY', 'Update strategies', 'root'),
('DELETE_STRATEGY', 'Delete strategies', 'root'),
('UPDATE_CONTEXT_FIELD', 'Update context fields', 'root'),
('CREATE_CONTEXT_FIELD', 'Create context fields', 'root'),
('DELETE_CONTEXT_FIELD', 'Delete context fields', 'root'),
('READ_ROLE', 'Read roles', 'root'),
('CREATE_FEATURE_STRATEGY', 'Create activation strategies', 'environment'),
('UPDATE_FEATURE_STRATEGY', 'Update activation strategies', 'environment'),
('DELETE_FEATURE_STRATEGY', 'Delete activation strategies', 'environment'),
('CREATE_CLIENT_API_TOKEN', 'Create CLIENT API tokens', 'root'),
('UPDATE_FEATURE_VARIANTS', 'Create/edit variants', 'project'),
('MOVE_FEATURE_TOGGLE', 'Change feature toggle project', 'project'),
('CREATE_SEGMENT', 'Create segments', 'root'),
('UPDATE_SEGMENT', 'Edit segments', 'root'),
('DELETE_SEGMENT', 'Delete segments', 'root'),
('READ_PROJECT_API_TOKEN', 'Read api tokens for a specific project', 'project'),
('CREATE_PROJECT_API_TOKEN', 'Create api tokens for a specific project', 'project'),
('DELETE_PROJECT_API_TOKEN', 'Delete api tokens for a specific project', 'project'),
('UPDATE_FEATURE_ENVIRONMENT_VARIANTS', 'Update variants', 'environment'),
('UPDATE_FEATURE_ENVIRONMENT', 'Enable/disable toggles', 'environment'),
('APPLY_CHANGE_REQUEST', 'Apply change requests', 'environment'),
('UPDATE_CLIENT_API_TOKEN', 'Update CLIENT API tokens', 'root'),
('UPDATE_PROJECT_SEGMENT', 'Create/edit project segment', 'project'),
('SKIP_CHANGE_REQUEST', 'Skip change request process', 'environment'),
('DELETE_CLIENT_API_TOKEN', 'Delete CLIENT API tokens', 'root'),
('READ_CLIENT_API_TOKEN', 'Read CLIENT API tokens', 'root'),
('APPROVE_CHANGE_REQUEST', 'Approve/Reject change requests', 'environment'),
('CREATE_FRONTEND_API_TOKEN', 'Create FRONTEND API tokens', 'root'),
('UPDATE_FRONTEND_API_TOKEN', 'Update FRONTEND API tokens', 'root'),
('DELETE_FRONTEND_API_TOKEN', 'Delete FRONTEND API tokens', 'root'),
('READ_FRONTEND_API_TOKEN', 'Read FRONTEND API tokens', 'root'),
('UPDATE_FEATURE_DEPENDENCY', 'Update feature dependency', 'project')
('CREATE_TAG_TYPE', 'Create tag types', 'root')
) AS new_permissions(permission, display_name, type)
WHERE NOT EXISTS (
SELECT 1 FROM permissions WHERE permission = new_permissions.permission
);
`,
cb
);

View File

@ -724,11 +724,9 @@ test('Should be denied access to delete a role that is in use', async () => {
const customRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 8,
name: 'DELETE_FEATURE',
},
]);
@ -963,14 +961,12 @@ test('Should allow user to take multiple group roles and have expected permissio
const createFeatureRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
]);
const deleteFeatureRole = await createRole([
{
id: 8,
name: 'DELETE_FEATURE',
},
]);
@ -1141,21 +1137,17 @@ test('if user has two roles user has union of permissions from the two roles', a
const firstRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 8,
name: 'DELETE_FEATURE',
},
]);
const secondRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 13,
name: 'UPDATE_PROJECT',
},
]);
@ -1182,21 +1174,17 @@ test('calling set for user overwrites existing roles', async () => {
const firstRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 8,
name: 'DELETE_FEATURE',
},
]);
const secondRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 13,
name: 'UPDATE_PROJECT',
},
]);
@ -1245,21 +1233,17 @@ test('if group has two roles user has union of permissions from the two roles',
const firstRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 8,
name: 'DELETE_FEATURE',
},
]);
const secondRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 13,
name: 'UPDATE_PROJECT',
},
]);
@ -1292,21 +1276,17 @@ test('calling set for group overwrites existing roles', async () => {
const firstRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 8,
name: 'DELETE_FEATURE',
},
]);
const secondRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 13,
name: 'UPDATE_PROJECT',
},
]);
@ -1361,7 +1341,6 @@ test('group with root role can be assigned a project specific role', async () =>
const firstRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
]);
@ -1428,7 +1407,6 @@ test('calling set roles for user with empty role array removes all roles', async
const role = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
]);
@ -1458,11 +1436,9 @@ test('calling set roles for user with empty role array should not remove root ro
const firstRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 8,
name: 'DELETE_FEATURE',
},
]);
@ -1493,18 +1469,15 @@ test('remove user access should remove all project roles', async () => {
const firstRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 8,
name: 'DELETE_FEATURE',
},
]);
const secondRole = await createRole([
{
id: 13,
name: 'UPDATE_PROJECT',
},
]);
@ -1535,18 +1508,15 @@ test('remove user access should remove all project roles, while leaving root rol
const firstRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 8,
name: 'DELETE_FEATURE',
},
]);
const secondRole = await createRole([
{
id: 13,
name: 'UPDATE_PROJECT',
},
]);
@ -1606,7 +1576,6 @@ test('calling set roles for group with empty role array removes all roles', asyn
const role = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
]);
@ -1648,11 +1617,9 @@ test('calling set roles for group with empty role array should not remove root r
const role = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 8,
name: 'DELETE_FEATURE',
},
]);
@ -1694,18 +1661,15 @@ test('remove group access should remove all project roles', async () => {
const firstRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 8,
name: 'DELETE_FEATURE',
},
]);
const secondRole = await createRole([
{
id: 13,
name: 'UPDATE_PROJECT',
},
]);
@ -1741,18 +1705,15 @@ test('remove group access should remove all project roles, while leaving root ro
const firstRole = await createRole([
{
id: 2,
name: 'CREATE_FEATURE',
},
{
id: 8,
name: 'DELETE_FEATURE',
},
]);
const secondRole = await createRole([
{
id: 13,
name: 'UPDATE_PROJECT',
},
]);
@ -1832,7 +1793,6 @@ test('access overview should have group access for groups that they are in', asy
const someGroupRole = await createRole([
{
id: 13,
name: 'UPDATE_PROJECT',
},
]);

View File

@ -3,7 +3,12 @@ import getLogger from '../../fixtures/no-logger';
import FeatureToggleService from '../../../lib/features/feature-toggle/feature-toggle-service';
import ProjectService from '../../../lib/services/project-service';
import { AccessService } from '../../../lib/services/access-service';
import { MOVE_FEATURE_TOGGLE } from '../../../lib/types/permissions';
import {
CREATE_FEATURE,
DELETE_FEATURE,
MOVE_FEATURE_TOGGLE,
UPDATE_FEATURE,
} from '../../../lib/types/permissions';
import { createTestConfig } from '../../config/test-config';
import { RoleName } from '../../../lib/types/model';
import { randomId } from '../../../lib/util/random-id';
@ -804,10 +809,10 @@ test('should add a user to the project with a custom role', async () => {
description: '',
permissions: [
{
id: 2, // CREATE_FEATURE
name: CREATE_FEATURE,
},
{
id: 8, // DELETE_FEATURE
name: DELETE_FEATURE,
},
],
});
@ -854,10 +859,10 @@ test('should delete role entries when deleting project', async () => {
description: '',
permissions: [
{
id: 2, // CREATE_FEATURE
name: CREATE_FEATURE,
},
{
id: 8, // DELETE_FEATURE
name: DELETE_FEATURE,
},
],
});
@ -894,10 +899,10 @@ test('should change a users role in the project', async () => {
description: '',
permissions: [
{
id: 2, // CREATE_FEATURE
name: CREATE_FEATURE,
},
{
id: 8, // DELETE_FEATURE
name: DELETE_FEATURE,
},
],
});
@ -1255,7 +1260,7 @@ test('Should allow bulk update of group permissions', async () => {
description: '',
permissions: [
{
id: 2, // CREATE_FEATURE
name: CREATE_FEATURE,
},
],
});
@ -1282,7 +1287,7 @@ test('Should bulk update of only users', async () => {
description: '',
permissions: [
{
id: 2, // CREATE_FEATURE
name: CREATE_FEATURE,
},
],
});
@ -1317,7 +1322,7 @@ test('Should allow bulk update of only groups', async () => {
description: '',
permissions: [
{
id: 2, // CREATE_FEATURE
name: CREATE_FEATURE,
},
],
});
@ -1366,7 +1371,7 @@ test('Should allow permutations of roles, groups and users when adding a new acc
description: '',
permissions: [
{
id: 2, // CREATE_FEATURE
name: CREATE_FEATURE,
},
],
});
@ -1376,7 +1381,7 @@ test('Should allow permutations of roles, groups and users when adding a new acc
description: '',
permissions: [
{
id: 7, // UPDATE_FEATURE
name: UPDATE_FEATURE,
},
],
});