1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-24 01:18:01 +02:00

refactor: favor permission name over id (#5409)

https://linear.app/unleash/issue/2-1664/create-db-migration-that-favors-the-name-column-over-id-for

Similar to https://github.com/Unleash/unleash/pull/5398, but
non-breaking (semver).
This keeps the permissions `id` column intact, however favors the
permission name whenever possible.
This commit is contained in:
Nuno Góis 2023-11-27 11:12:09 +00:00 committed by GitHub
parent b021e7cf85
commit 023db4e2c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1216 additions and 92 deletions

View File

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

View File

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

View File

@ -13,8 +13,8 @@ import cloneDeep from 'lodash.clonedeep';
export const getRoleKey = (permission: IPermission): string => { export const getRoleKey = (permission: IPermission): string => {
return permission.environment return permission.environment
? `${permission.id}-${permission.environment}` ? `${permission.name}-${permission.environment}`
: `${permission.id}`; : `${permission.name}`;
}; };
export const permissionsToCheckedPermissions = ( export const permissionsToCheckedPermissions = (

View File

@ -39,15 +39,14 @@ test('resolvePermissions returns empty list if empty list', async () => {
}); });
test.each([ test.each([
args([{ id: 1 }]), args([{ name: 'CREATE_CONTEXT_FIELD' }]),
args([{ id: 4, environment: 'development' }]), args([{ name: 'CREATE_FEATURE', environment: 'development' }]),
args([{ id: 4, name: 'should keep the id' }]),
args([ args([
{ id: 1, environment: 'development' }, { name: 'CREATE_CONTEXT_FIELD' },
{ id: 2, name: 'ignore this name' }, { name: 'CREATE_FEATURE', environment: 'development' },
]), ]),
])( ])(
'resolvePermissions with permission ids (%o) returns the list unmodified', 'resolvePermissions with permission names (%o) will return the list unmodified',
async (permissions) => { async (permissions) => {
const access = db.stores.accessStore as AccessStore; const access = db.stores.accessStore as AccessStore;
const result = await access.resolvePermissions(permissions); const result = await access.resolvePermissions(permissions);
@ -56,26 +55,23 @@ test.each([
); );
test.each([ test.each([
args([{ id: 1 }], [{ id: 1, name: 'ADMIN' }]),
args( args(
[{ name: 'CREATE_CONTEXT_FIELD' }], [{ id: 4, environment: 'development' }],
[{ id: 18, name: 'CREATE_CONTEXT_FIELD' }], [{ id: 4, name: 'CREATE_ADDON', environment: 'development' }],
),
args(
[{ name: 'CREATE_FEATURE', environment: 'development' }],
[{ id: 2, name: 'CREATE_FEATURE', environment: 'development' }],
), ),
args( args(
[ [
{ name: 'CREATE_CONTEXT_FIELD' }, { id: 1, environment: 'development' },
{ name: 'CREATE_FEATURE', environment: 'development' }, { id: 2, name: 'ignore this name' },
], ],
[ [
{ id: 18, name: 'CREATE_CONTEXT_FIELD' }, { id: 1, name: 'ADMIN', environment: 'development' },
{ id: 2, name: 'CREATE_FEATURE', environment: 'development' }, { id: 2, name: 'ignore this name' },
], ],
), ),
])( ])(
'resolvePermissions with permission names (%o) will inject the ids', 'resolvePermissions with only permission ids (%o) will resolve to named permissions without an id',
async (permissions, expected) => { async (permissions, expected) => {
const access = db.stores.accessStore as AccessStore; const access = db.stores.accessStore as AccessStore;
const result = await access.resolvePermissions(permissions); const result = await access.resolvePermissions(permissions);
@ -93,15 +89,15 @@ test.each([
{ name: 'UPDATE_FEATURE', environment: 'development' }, { name: 'UPDATE_FEATURE', environment: 'development' },
], ],
[ [
{ id: 18, name: 'CREATE_CONTEXT_FIELD' }, { name: 'CREATE_CONTEXT_FIELD' },
{ id: 3 }, { id: 3, name: 'CREATE_STRATEGY' },
{ id: 2, name: 'CREATE_FEATURE', environment: 'development' }, { name: 'CREATE_FEATURE', environment: 'development' },
{ id: 15, environment: 'development' }, { id: 15, name: 'UPDATE_STRATEGY', environment: 'development' },
{ id: 7, name: 'UPDATE_FEATURE', environment: 'development' }, { name: 'UPDATE_FEATURE', environment: 'development' },
], ],
), ),
])( ])(
'resolvePermissions mixed ids and names (%o) will inject the ids where they are missing', 'resolvePermissions mixed ids and names (%o) will inject the names where they are missing',
async (permissions, expected) => { async (permissions, expected) => {
const access = db.stores.accessStore as AccessStore; const access = db.stores.accessStore as AccessStore;
const result = await access.resolvePermissions(permissions); const result = await access.resolvePermissions(permissions);

View File

@ -51,11 +51,7 @@ interface IPermissionRow {
role_id: number; role_id: number;
} }
type ResolvedPermission = { type NameAndIdPermission = NamePermissionRef & IdPermissionRef;
id: number;
name?: string;
environment?: string;
};
export class AccessStore implements IAccessStore { export class AccessStore implements IAccessStore {
private logger: Logger; private logger: Logger;
@ -74,70 +70,71 @@ export class AccessStore implements IAccessStore {
}); });
} }
private permissionHasId = (permission: PermissionRef): boolean => { private permissionHasName = (permission: PermissionRef): boolean => {
return (permission as IdPermissionRef).id !== undefined; return (permission as NamePermissionRef).name !== undefined;
}; };
private permissionNamesToIds = async ( private permissionIdsToNames = async (
permissions: NamePermissionRef[], permissions: IdPermissionRef[],
): Promise<ResolvedPermission[]> => { ): Promise<NameAndIdPermission[]> => {
const permissionNames = (permissions ?? []) const permissionIds = (permissions ?? [])
.filter((p) => p.name !== undefined) .filter((p) => p.id !== undefined)
.map((p) => p.name); .map((p) => p.id);
if (permissionNames.length === 0) { if (permissionIds.length === 0) {
return []; return [];
} }
const stopTimer = this.timer('permissionNamesToIds'); const stopTimer = this.timer('permissionIdsToNames');
const rows = await this.db const rows = await this.db
.select('id', 'permission') .select('id', 'permission')
.from(T.PERMISSIONS) .from(T.PERMISSIONS)
.whereIn('permission', permissionNames); .whereIn('id', permissionIds);
const rowByPermissionName = rows.reduce((acc, row) => { const rowByPermissionId = rows.reduce((acc, row) => {
acc[row.permission] = row; acc[row.id] = row;
return acc; return acc;
}, {} as Map<string, IPermissionRow>); }, {} as Map<string, IPermissionRow>);
const permissionsWithIds = permissions.map((permission) => ({ const permissionsWithNames = permissions.map((permission) => ({
id: rowByPermissionName[permission.name].id, name: rowByPermissionId[permission.id].permission,
...permission, ...permission,
})); }));
stopTimer(); stopTimer();
return permissionsWithIds; return permissionsWithNames;
}; };
resolvePermissions = async ( resolvePermissions = async (
permissions: PermissionRef[], permissions: PermissionRef[],
): Promise<ResolvedPermission[]> => { ): Promise<NamePermissionRef[]> => {
if (permissions === undefined || permissions.length === 0) { if (permissions === undefined || permissions.length === 0) {
return []; return [];
} }
// permissions without ids (just names) // permissions without names (just ids)
const permissionsWithoutIds = permissions.filter( const permissionsWithoutNames = permissions.filter(
(p) => !this.permissionHasId(p), (p) => !this.permissionHasName(p),
) as NamePermissionRef[]; ) as IdPermissionRef[];
const idPermissionsFromNamed = await this.permissionNamesToIds(
permissionsWithoutIds,
);
if (permissionsWithoutIds.length === permissions.length) { if (permissionsWithoutNames.length === permissions.length) {
// all named permissions without ids // all permissions without names
return idPermissionsFromNamed; return await this.permissionIdsToNames(permissionsWithoutNames);
} else if (permissionsWithoutIds.length === 0) { } else if (permissionsWithoutNames.length === 0) {
// all permissions have ids // all permissions have names
return permissions as ResolvedPermission[]; return permissions as NamePermissionRef[];
} }
// some permissions have ids, some don't (should not happen!)
// some permissions have names, some don't (should not happen!)
const namedPermissionsFromIds = await this.permissionIdsToNames(
permissionsWithoutNames,
);
return permissions.map((permission) => { return permissions.map((permission) => {
if (this.permissionHasId(permission)) { if (this.permissionHasName(permission)) {
return permission as ResolvedPermission; return permission as NamePermissionRef;
} else { } else {
return idPermissionsFromNamed.find( return namedPermissionsFromIds.find(
(p) => p.name === (permission as NamePermissionRef).name, (p) => p.id === (permission as IdPermissionRef).id,
)!; )!;
} }
}); });
@ -204,20 +201,20 @@ export class AccessStore implements IAccessStore {
let userPermissionQuery = this.db let userPermissionQuery = this.db
.select( .select(
'project', 'project',
'permission', 'rp.permission',
'environment', 'environment',
'type', 'type',
'ur.role_id', 'ur.role_id',
) )
.from<IPermissionRow>(`${T.ROLE_PERMISSION} AS rp`) .from<IPermissionRow>(`${T.ROLE_PERMISSION} AS rp`)
.join(`${T.ROLE_USER} AS ur`, 'ur.role_id', 'rp.role_id') .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); .where('ur.user_id', '=', userId);
userPermissionQuery = userPermissionQuery.union((db) => { userPermissionQuery = userPermissionQuery.union((db) => {
db.select( db.select(
'project', 'project',
'permission', 'rp.permission',
'environment', 'environment',
'p.type', 'p.type',
'gr.role_id', 'gr.role_id',
@ -226,14 +223,14 @@ export class AccessStore implements IAccessStore {
.join(`${T.GROUPS} AS g`, 'g.id', 'gu.group_id') .join(`${T.GROUPS} AS g`, 'g.id', 'gu.group_id')
.join(`${T.GROUP_ROLE} AS gr`, 'gu.group_id', 'gr.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.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); .andWhere('gu.user_id', '=', userId);
}); });
userPermissionQuery = userPermissionQuery.union((db) => { userPermissionQuery = userPermissionQuery.union((db) => {
db.select( db.select(
this.db.raw("'default' as project"), this.db.raw("'default' as project"),
'permission', 'rp.permission',
'environment', 'environment',
'p.type', 'p.type',
'g.root_role_id as role_id', 'g.root_role_id as role_id',
@ -245,7 +242,7 @@ export class AccessStore implements IAccessStore {
'rp.role_id', 'rp.role_id',
'g.root_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') .whereNotNull('g.root_role_id')
.andWhere('gu.user_id', '=', userId); .andWhere('gu.user_id', '=', userId);
}); });
@ -280,13 +277,13 @@ export class AccessStore implements IAccessStore {
const rows = await this.db const rows = await this.db
.select( .select(
'p.id', 'p.id',
'p.permission', 'rp.permission',
'rp.environment', 'rp.environment',
'p.display_name', 'p.display_name',
'p.type', 'p.type',
) )
.from<IPermission>(`${T.ROLE_PERMISSION} as rp`) .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); .where('rp.role_id', '=', roleId);
stopTimer(); stopTimer();
return rows.map((permission) => { return rows.map((permission) => {
@ -304,12 +301,12 @@ export class AccessStore implements IAccessStore {
role_id: number, role_id: number,
permissions: PermissionRef[], permissions: PermissionRef[],
): Promise<void> { ): Promise<void> {
const resolvedPermission = await this.resolvePermissions(permissions); const resolvedPermissions = await this.resolvePermissions(permissions);
const rows = resolvedPermission.map((permission) => { const rows = resolvedPermissions.map((permission) => {
return { return {
role_id, role_id,
permission_id: permission.id, permission: permission.name,
environment: permission.environment, environment: permission.environment,
}; };
}); });
@ -808,14 +805,14 @@ export class AccessStore implements IAccessStore {
} }
}); });
// no need to pass down the environment in this particular case because it'll be overriden // no need to pass down the environment in this particular case because it'll be overriden
const permissionsWithIds = await this.resolvePermissions( const permissionsWithNames = await this.resolvePermissions(
permissionsAsRefs, permissionsAsRefs,
); );
const newRoles = permissionsWithIds.map((p) => ({ const newRoles = permissionsWithNames.map((p) => ({
role_id, role_id,
environment, environment,
permission_id: p.id, permission: p.name,
})); }));
return this.db.batchInsert(T.ROLE_PERMISSION, newRoles); return this.db.batchInsert(T.ROLE_PERMISSION, newRoles);
@ -826,17 +823,10 @@ export class AccessStore implements IAccessStore {
permission: string, permission: string,
environment?: string, environment?: string,
): Promise<void> { ): 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) return this.db(T.ROLE_PERMISSION)
.where({ .where({
role_id, role_id,
permission_id: permissionId, permission,
environment, environment,
}) })
.delete(); .delete();
@ -856,8 +846,8 @@ export class AccessStore implements IAccessStore {
): Promise<void> { ): Promise<void> {
return this.db.raw( return this.db.raw(
`insert into role_permission `insert into role_permission
(role_id, permission_id, environment) (role_id, permission, environment)
(select role_id, permission_id, ? (select role_id, permission, ?
from ${T.ROLE_PERMISSION} where environment = ?)`, from ${T.ROLE_PERMISSION} where environment = ?)`,
[destinationEnvironment, sourceEnvironment], [destinationEnvironment, sourceEnvironment],
); );

View File

@ -0,0 +1,245 @@
exports.up = function (db, cb) {
db.runSql(
`
-- STEP 1: Ensure 'permission' column contains unique values
ALTER TABLE permissions
ADD CONSTRAINT permission_unique UNIQUE (permission);
-- STEP 2: Add a new column for the new foreign key
ALTER TABLE role_permission
ADD COLUMN permission text;
-- 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 4: Drop the 'id' primary key constraint
ALTER TABLE permissions
DROP CONSTRAINT permissions_pkey;
-- STEP 5: Add the 'permission' primary key constraint
ALTER TABLE permissions
ADD PRIMARY KEY (permission);
-- STEP 6: 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 7: Update the assign_unleash_permission_to_role function to use permission name instead of id
CREATE OR REPLACE FUNCTION assign_unleash_permission_to_role(permission_name text, role_name text) returns void as
$$
declare
var_role_id int;
var_permission text;
BEGIN
var_role_id := (SELECT r.id FROM roles r WHERE r.name = role_name);
var_permission := (SELECT p.permission FROM permissions p WHERE p.permission = permission_name);
IF NOT EXISTS (
SELECT 1
FROM role_permission AS rp
WHERE rp.role_id = var_role_id AND rp.permission = var_permission
) THEN
INSERT INTO role_permission(role_id, permission) VALUES (var_role_id, var_permission);
END IF;
END
$$ language plpgsql;
-- STEP 8: Create a new assign_unleash_permission_to_role_for_all_environments function
CREATE OR REPLACE FUNCTION assign_unleash_permission_to_role_for_all_environments(permission_name text, role_name text) returns void as
$$
declare
var_role_id int;
var_permission text;
BEGIN
var_role_id := (SELECT id FROM roles r WHERE r.name = role_name);
var_permission := (SELECT p.permission FROM permissions p WHERE p.permission = permission_name);
INSERT INTO role_permission (role_id, permission, environment)
SELECT var_role_id, var_permission, e.name
FROM environments e
WHERE NOT EXISTS (
SELECT 1
FROM role_permission rp
WHERE rp.role_id = var_role_id
AND rp.permission = var_permission
AND rp.environment = e.name
);
END;
$$ LANGUAGE plpgsql;
-- STEP 9: 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
);
-- STEP 10: Ensure the default roles exist
INSERT INTO roles (name, description, type)
SELECT * FROM (VALUES
('Admin', 'Users with the root admin role have superuser access to Unleash and can perform any operation within the Unleash platform.', 'root'),
('Editor', 'Users with the root editor role have access to most features in Unleash, but can not manage users and roles in the root scope. Editors will be added as project owners when creating projects and get superuser rights within the context of these projects. Users with the editor role will also get access to most permissions on the default project by default.', 'root'),
('Viewer', 'Users with the root viewer role can only read root resources in Unleash. Viewers can be added to specific projects as project members. Users with the viewer role may not view API tokens.', 'root'),
('Owner', 'Users with the project owner role have full control over the project, and can add and manage other users within the project context, manage feature toggles within the project, and control advanced project features like archiving and deleting the project.', 'project'),
('Member', 'Users with the project member role are allowed to view, create, and update feature toggles within a project, but have limited permissions in regards to managing the project''s user access and can not archive or delete the project.', 'project')
) AS new_roles(name, description)
WHERE NOT EXISTS (
SELECT 1 FROM roles WHERE name = new_roles.name
);
-- STEP 11: Ensure the default roles have the correct permissions
SELECT assign_unleash_permission_to_role('ADMIN', 'Admin');
SELECT assign_unleash_permission_to_role('CREATE_FEATURE', 'Editor');
SELECT assign_unleash_permission_to_role('CREATE_STRATEGY', 'Editor');
SELECT assign_unleash_permission_to_role('CREATE_ADDON', 'Editor');
SELECT assign_unleash_permission_to_role('DELETE_ADDON', 'Editor');
SELECT assign_unleash_permission_to_role('UPDATE_ADDON', 'Editor');
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE', 'Editor');
SELECT assign_unleash_permission_to_role('DELETE_FEATURE', 'Editor');
SELECT assign_unleash_permission_to_role('UPDATE_APPLICATION', 'Editor');
SELECT assign_unleash_permission_to_role('UPDATE_TAG_TYPE', 'Editor');
SELECT assign_unleash_permission_to_role('DELETE_TAG_TYPE', 'Editor');
SELECT assign_unleash_permission_to_role('CREATE_PROJECT', 'Editor');
SELECT assign_unleash_permission_to_role('UPDATE_PROJECT', 'Editor');
SELECT assign_unleash_permission_to_role('DELETE_PROJECT', 'Editor');
SELECT assign_unleash_permission_to_role('UPDATE_STRATEGY', 'Editor');
SELECT assign_unleash_permission_to_role('DELETE_STRATEGY', 'Editor');
SELECT assign_unleash_permission_to_role('UPDATE_CONTEXT_FIELD', 'Editor');
SELECT assign_unleash_permission_to_role('CREATE_CONTEXT_FIELD', 'Editor');
SELECT assign_unleash_permission_to_role('DELETE_CONTEXT_FIELD', 'Editor');
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE_VARIANTS', 'Editor');
SELECT assign_unleash_permission_to_role_for_all_environments('CREATE_FEATURE_STRATEGY', 'Editor');
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_STRATEGY', 'Editor');
SELECT assign_unleash_permission_to_role_for_all_environments('DELETE_FEATURE_STRATEGY', 'Editor');
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_ENVIRONMENT', 'Editor');
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_ENVIRONMENT_VARIANTS', 'Editor');
SELECT assign_unleash_permission_to_role('MOVE_FEATURE_TOGGLE', 'Editor');
SELECT assign_unleash_permission_to_role('CREATE_SEGMENT', 'Editor');
SELECT assign_unleash_permission_to_role('UPDATE_SEGMENT', 'Editor');
SELECT assign_unleash_permission_to_role('DELETE_SEGMENT', 'Editor');
SELECT assign_unleash_permission_to_role('READ_PROJECT_API_TOKEN', 'Editor');
SELECT assign_unleash_permission_to_role('CREATE_PROJECT_API_TOKEN', 'Editor');
SELECT assign_unleash_permission_to_role('DELETE_PROJECT_API_TOKEN', 'Editor');
SELECT assign_unleash_permission_to_role('READ_CLIENT_API_TOKEN', 'Editor');
SELECT assign_unleash_permission_to_role('READ_FRONTEND_API_TOKEN', 'Editor');
SELECT assign_unleash_permission_to_role('CREATE_TAG_TYPE', 'Editor');
SELECT assign_unleash_permission_to_role('CREATE_FEATURE', 'Owner');
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE', 'Owner');
SELECT assign_unleash_permission_to_role('DELETE_FEATURE', 'Owner');
SELECT assign_unleash_permission_to_role('UPDATE_PROJECT', 'Owner');
SELECT assign_unleash_permission_to_role('DELETE_PROJECT', 'Owner');
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE_VARIANTS', 'Owner');
SELECT assign_unleash_permission_to_role_for_all_environments('CREATE_FEATURE_STRATEGY', 'Owner');
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_STRATEGY', 'Owner');
SELECT assign_unleash_permission_to_role_for_all_environments('DELETE_FEATURE_STRATEGY', 'Owner');
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_ENVIRONMENT', 'Owner');
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_ENVIRONMENT_VARIANTS', 'Owner');
SELECT assign_unleash_permission_to_role('MOVE_FEATURE_TOGGLE', 'Owner');
SELECT assign_unleash_permission_to_role('READ_PROJECT_API_TOKEN', 'Owner');
SELECT assign_unleash_permission_to_role('CREATE_PROJECT_API_TOKEN', 'Owner');
SELECT assign_unleash_permission_to_role('DELETE_PROJECT_API_TOKEN', 'Owner');
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE_DEPENDENCY', 'Owner');
SELECT assign_unleash_permission_to_role('CREATE_FEATURE', 'Member');
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE', 'Member');
SELECT assign_unleash_permission_to_role('DELETE_FEATURE', 'Member');
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE_VARIANTS', 'Member');
SELECT assign_unleash_permission_to_role_for_all_environments('CREATE_FEATURE_STRATEGY', 'Member');
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_STRATEGY', 'Member');
SELECT assign_unleash_permission_to_role_for_all_environments('DELETE_FEATURE_STRATEGY', 'Member');
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_ENVIRONMENT', 'Member');
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_ENVIRONMENT_VARIANTS', 'Member');
SELECT assign_unleash_permission_to_role('READ_PROJECT_API_TOKEN', 'Member');
SELECT assign_unleash_permission_to_role('CREATE_PROJECT_API_TOKEN', 'Member');
SELECT assign_unleash_permission_to_role('DELETE_PROJECT_API_TOKEN', 'Member');
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE_DEPENDENCY', 'Member');
`,
cb
);
};
exports.down = function (db, cb) {
db.runSql(
`
-- STEP 1: Undo foreign key constraint on 'role_permission'
ALTER TABLE role_permission
DROP CONSTRAINT fk_role_permission_permission;
-- STEP 2: Undo primary key constraint on 'permissions'
ALTER TABLE permissions
DROP CONSTRAINT permissions_pkey;
-- STEP 3: Add the 'id' primary key constraint
ALTER TABLE permissions
ADD PRIMARY KEY (id);
-- STEP 4: Re-add the permissions by id
UPDATE role_permission rp
SET permission_id = p.id
FROM permissions p
WHERE rp.permission = p.permission;
-- STEP 5: Drop the new 'permission' column
ALTER TABLE role_permission
DROP COLUMN permission;
-- STEP 6: Drop the unique constraint on 'permission'
ALTER TABLE permissions
DROP CONSTRAINT permission_unique;
`,
cb
);
};

View File

@ -0,0 +1,736 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Favor permission name over id migration correctly assigns permissions by name 1`] = `
[
{
"display_name": "A test permission",
"permission": "TEST_PERMISSION_1",
"type": "root",
},
{
"display_name": "A test permission",
"permission": "TEST_PERMISSION_2",
"type": "root",
},
{
"display_name": "A test permission",
"permission": "TEST_PERMISSION_3",
"type": "root",
},
{
"display_name": "A test permission",
"permission": "TEST_PERMISSION_4",
"type": "root",
},
{
"display_name": "Admin",
"permission": "ADMIN",
"type": "root",
},
{
"display_name": "Create feature toggles",
"permission": "CREATE_FEATURE",
"type": "project",
},
{
"display_name": "Create activation strategies",
"permission": "CREATE_STRATEGY",
"type": "root",
},
{
"display_name": "Create addons",
"permission": "CREATE_ADDON",
"type": "root",
},
{
"display_name": "Delete addons",
"permission": "DELETE_ADDON",
"type": "root",
},
{
"display_name": "Update addons",
"permission": "UPDATE_ADDON",
"type": "root",
},
{
"display_name": "Update feature toggles",
"permission": "UPDATE_FEATURE",
"type": "project",
},
{
"display_name": "Delete feature toggles",
"permission": "DELETE_FEATURE",
"type": "project",
},
{
"display_name": "Update applications",
"permission": "UPDATE_APPLICATION",
"type": "root",
},
{
"display_name": "Update tag types",
"permission": "UPDATE_TAG_TYPE",
"type": "root",
},
{
"display_name": "Delete tag types",
"permission": "DELETE_TAG_TYPE",
"type": "root",
},
{
"display_name": "Create projects",
"permission": "CREATE_PROJECT",
"type": "root",
},
{
"display_name": "Update project",
"permission": "UPDATE_PROJECT",
"type": "project",
},
{
"display_name": "Delete project",
"permission": "DELETE_PROJECT",
"type": "project",
},
{
"display_name": "Update strategies",
"permission": "UPDATE_STRATEGY",
"type": "root",
},
{
"display_name": "Delete strategies",
"permission": "DELETE_STRATEGY",
"type": "root",
},
{
"display_name": "Update context fields",
"permission": "UPDATE_CONTEXT_FIELD",
"type": "root",
},
{
"display_name": "Create context fields",
"permission": "CREATE_CONTEXT_FIELD",
"type": "root",
},
{
"display_name": "Delete context fields",
"permission": "DELETE_CONTEXT_FIELD",
"type": "root",
},
{
"display_name": "Read roles",
"permission": "READ_ROLE",
"type": "root",
},
{
"display_name": "Create activation strategies",
"permission": "CREATE_FEATURE_STRATEGY",
"type": "environment",
},
{
"display_name": "Update activation strategies",
"permission": "UPDATE_FEATURE_STRATEGY",
"type": "environment",
},
{
"display_name": "Delete activation strategies",
"permission": "DELETE_FEATURE_STRATEGY",
"type": "environment",
},
{
"display_name": "Create CLIENT API tokens",
"permission": "CREATE_CLIENT_API_TOKEN",
"type": "root",
},
{
"display_name": "Create/edit variants",
"permission": "UPDATE_FEATURE_VARIANTS",
"type": "project",
},
{
"display_name": "Change feature toggle project",
"permission": "MOVE_FEATURE_TOGGLE",
"type": "project",
},
{
"display_name": "Create segments",
"permission": "CREATE_SEGMENT",
"type": "root",
},
{
"display_name": "Edit segments",
"permission": "UPDATE_SEGMENT",
"type": "root",
},
{
"display_name": "Delete segments",
"permission": "DELETE_SEGMENT",
"type": "root",
},
{
"display_name": "Read api tokens for a specific project",
"permission": "READ_PROJECT_API_TOKEN",
"type": "project",
},
{
"display_name": "Create api tokens for a specific project",
"permission": "CREATE_PROJECT_API_TOKEN",
"type": "project",
},
{
"display_name": "Delete api tokens for a specific project",
"permission": "DELETE_PROJECT_API_TOKEN",
"type": "project",
},
{
"display_name": "Update variants",
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
"type": "environment",
},
{
"display_name": "Enable/disable toggles",
"permission": "UPDATE_FEATURE_ENVIRONMENT",
"type": "environment",
},
{
"display_name": "Apply change requests",
"permission": "APPLY_CHANGE_REQUEST",
"type": "environment",
},
{
"display_name": "Update CLIENT API tokens",
"permission": "UPDATE_CLIENT_API_TOKEN",
"type": "root",
},
{
"display_name": "Create/edit project segment",
"permission": "UPDATE_PROJECT_SEGMENT",
"type": "project",
},
{
"display_name": "Skip change request process",
"permission": "SKIP_CHANGE_REQUEST",
"type": "environment",
},
{
"display_name": "Delete CLIENT API tokens",
"permission": "DELETE_CLIENT_API_TOKEN",
"type": "root",
},
{
"display_name": "Read CLIENT API tokens",
"permission": "READ_CLIENT_API_TOKEN",
"type": "root",
},
{
"display_name": "Approve/Reject change requests",
"permission": "APPROVE_CHANGE_REQUEST",
"type": "environment",
},
{
"display_name": "Create FRONTEND API tokens",
"permission": "CREATE_FRONTEND_API_TOKEN",
"type": "root",
},
{
"display_name": "Update FRONTEND API tokens",
"permission": "UPDATE_FRONTEND_API_TOKEN",
"type": "root",
},
{
"display_name": "Delete FRONTEND API tokens",
"permission": "DELETE_FRONTEND_API_TOKEN",
"type": "root",
},
{
"display_name": "Read FRONTEND API tokens",
"permission": "READ_FRONTEND_API_TOKEN",
"type": "root",
},
{
"display_name": "Update feature dependency",
"permission": "UPDATE_FEATURE_DEPENDENCY",
"type": "project",
},
{
"display_name": "Create tag types",
"permission": "CREATE_TAG_TYPE",
"type": "root",
},
]
`;
exports[`Favor permission name over id migration correctly assigns permissions by name 2`] = `
[
{
"environment": null,
"permission": "ADMIN",
"role_id": 6,
},
{
"environment": null,
"permission": "CREATE_FEATURE",
"role_id": 7,
},
{
"environment": null,
"permission": "CREATE_STRATEGY",
"role_id": 7,
},
{
"environment": null,
"permission": "CREATE_ADDON",
"role_id": 7,
},
{
"environment": null,
"permission": "DELETE_ADDON",
"role_id": 7,
},
{
"environment": null,
"permission": "UPDATE_ADDON",
"role_id": 7,
},
{
"environment": null,
"permission": "UPDATE_FEATURE",
"role_id": 7,
},
{
"environment": null,
"permission": "DELETE_FEATURE",
"role_id": 7,
},
{
"environment": null,
"permission": "UPDATE_APPLICATION",
"role_id": 7,
},
{
"environment": null,
"permission": "UPDATE_TAG_TYPE",
"role_id": 7,
},
{
"environment": null,
"permission": "DELETE_TAG_TYPE",
"role_id": 7,
},
{
"environment": null,
"permission": "CREATE_PROJECT",
"role_id": 7,
},
{
"environment": null,
"permission": "UPDATE_PROJECT",
"role_id": 7,
},
{
"environment": null,
"permission": "DELETE_PROJECT",
"role_id": 7,
},
{
"environment": null,
"permission": "UPDATE_STRATEGY",
"role_id": 7,
},
{
"environment": null,
"permission": "DELETE_STRATEGY",
"role_id": 7,
},
{
"environment": null,
"permission": "UPDATE_CONTEXT_FIELD",
"role_id": 7,
},
{
"environment": null,
"permission": "CREATE_CONTEXT_FIELD",
"role_id": 7,
},
{
"environment": null,
"permission": "DELETE_CONTEXT_FIELD",
"role_id": 7,
},
{
"environment": null,
"permission": "UPDATE_FEATURE_VARIANTS",
"role_id": 7,
},
{
"environment": "default",
"permission": "CREATE_FEATURE_STRATEGY",
"role_id": 7,
},
{
"environment": "development",
"permission": "CREATE_FEATURE_STRATEGY",
"role_id": 7,
},
{
"environment": "production",
"permission": "CREATE_FEATURE_STRATEGY",
"role_id": 7,
},
{
"environment": "default",
"permission": "UPDATE_FEATURE_STRATEGY",
"role_id": 7,
},
{
"environment": "development",
"permission": "UPDATE_FEATURE_STRATEGY",
"role_id": 7,
},
{
"environment": "production",
"permission": "UPDATE_FEATURE_STRATEGY",
"role_id": 7,
},
{
"environment": "default",
"permission": "DELETE_FEATURE_STRATEGY",
"role_id": 7,
},
{
"environment": "development",
"permission": "DELETE_FEATURE_STRATEGY",
"role_id": 7,
},
{
"environment": "production",
"permission": "DELETE_FEATURE_STRATEGY",
"role_id": 7,
},
{
"environment": "default",
"permission": "UPDATE_FEATURE_ENVIRONMENT",
"role_id": 7,
},
{
"environment": "development",
"permission": "UPDATE_FEATURE_ENVIRONMENT",
"role_id": 7,
},
{
"environment": "production",
"permission": "UPDATE_FEATURE_ENVIRONMENT",
"role_id": 7,
},
{
"environment": "default",
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
"role_id": 7,
},
{
"environment": "development",
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
"role_id": 7,
},
{
"environment": "production",
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
"role_id": 7,
},
{
"environment": null,
"permission": "MOVE_FEATURE_TOGGLE",
"role_id": 7,
},
{
"environment": null,
"permission": "CREATE_SEGMENT",
"role_id": 7,
},
{
"environment": null,
"permission": "UPDATE_SEGMENT",
"role_id": 7,
},
{
"environment": null,
"permission": "DELETE_SEGMENT",
"role_id": 7,
},
{
"environment": null,
"permission": "READ_PROJECT_API_TOKEN",
"role_id": 7,
},
{
"environment": null,
"permission": "CREATE_PROJECT_API_TOKEN",
"role_id": 7,
},
{
"environment": null,
"permission": "DELETE_PROJECT_API_TOKEN",
"role_id": 7,
},
{
"environment": null,
"permission": "READ_CLIENT_API_TOKEN",
"role_id": 7,
},
{
"environment": null,
"permission": "READ_FRONTEND_API_TOKEN",
"role_id": 7,
},
{
"environment": null,
"permission": "CREATE_TAG_TYPE",
"role_id": 7,
},
{
"environment": null,
"permission": "CREATE_FEATURE",
"role_id": 9,
},
{
"environment": null,
"permission": "UPDATE_FEATURE",
"role_id": 9,
},
{
"environment": null,
"permission": "DELETE_FEATURE",
"role_id": 9,
},
{
"environment": null,
"permission": "UPDATE_PROJECT",
"role_id": 9,
},
{
"environment": null,
"permission": "DELETE_PROJECT",
"role_id": 9,
},
{
"environment": null,
"permission": "UPDATE_FEATURE_VARIANTS",
"role_id": 9,
},
{
"environment": "default",
"permission": "CREATE_FEATURE_STRATEGY",
"role_id": 9,
},
{
"environment": "development",
"permission": "CREATE_FEATURE_STRATEGY",
"role_id": 9,
},
{
"environment": "production",
"permission": "CREATE_FEATURE_STRATEGY",
"role_id": 9,
},
{
"environment": "default",
"permission": "UPDATE_FEATURE_STRATEGY",
"role_id": 9,
},
{
"environment": "development",
"permission": "UPDATE_FEATURE_STRATEGY",
"role_id": 9,
},
{
"environment": "production",
"permission": "UPDATE_FEATURE_STRATEGY",
"role_id": 9,
},
{
"environment": "default",
"permission": "DELETE_FEATURE_STRATEGY",
"role_id": 9,
},
{
"environment": "development",
"permission": "DELETE_FEATURE_STRATEGY",
"role_id": 9,
},
{
"environment": "production",
"permission": "DELETE_FEATURE_STRATEGY",
"role_id": 9,
},
{
"environment": "default",
"permission": "UPDATE_FEATURE_ENVIRONMENT",
"role_id": 9,
},
{
"environment": "development",
"permission": "UPDATE_FEATURE_ENVIRONMENT",
"role_id": 9,
},
{
"environment": "production",
"permission": "UPDATE_FEATURE_ENVIRONMENT",
"role_id": 9,
},
{
"environment": "default",
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
"role_id": 9,
},
{
"environment": "development",
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
"role_id": 9,
},
{
"environment": "production",
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
"role_id": 9,
},
{
"environment": null,
"permission": "MOVE_FEATURE_TOGGLE",
"role_id": 9,
},
{
"environment": null,
"permission": "READ_PROJECT_API_TOKEN",
"role_id": 9,
},
{
"environment": null,
"permission": "CREATE_PROJECT_API_TOKEN",
"role_id": 9,
},
{
"environment": null,
"permission": "DELETE_PROJECT_API_TOKEN",
"role_id": 9,
},
{
"environment": null,
"permission": "UPDATE_FEATURE_DEPENDENCY",
"role_id": 9,
},
{
"environment": null,
"permission": "CREATE_FEATURE",
"role_id": 10,
},
{
"environment": null,
"permission": "UPDATE_FEATURE",
"role_id": 10,
},
{
"environment": null,
"permission": "DELETE_FEATURE",
"role_id": 10,
},
{
"environment": null,
"permission": "UPDATE_FEATURE_VARIANTS",
"role_id": 10,
},
{
"environment": "default",
"permission": "CREATE_FEATURE_STRATEGY",
"role_id": 10,
},
{
"environment": "development",
"permission": "CREATE_FEATURE_STRATEGY",
"role_id": 10,
},
{
"environment": "production",
"permission": "CREATE_FEATURE_STRATEGY",
"role_id": 10,
},
{
"environment": "default",
"permission": "UPDATE_FEATURE_STRATEGY",
"role_id": 10,
},
{
"environment": "development",
"permission": "UPDATE_FEATURE_STRATEGY",
"role_id": 10,
},
{
"environment": "production",
"permission": "UPDATE_FEATURE_STRATEGY",
"role_id": 10,
},
{
"environment": "default",
"permission": "DELETE_FEATURE_STRATEGY",
"role_id": 10,
},
{
"environment": "development",
"permission": "DELETE_FEATURE_STRATEGY",
"role_id": 10,
},
{
"environment": "production",
"permission": "DELETE_FEATURE_STRATEGY",
"role_id": 10,
},
{
"environment": "default",
"permission": "UPDATE_FEATURE_ENVIRONMENT",
"role_id": 10,
},
{
"environment": "development",
"permission": "UPDATE_FEATURE_ENVIRONMENT",
"role_id": 10,
},
{
"environment": "production",
"permission": "UPDATE_FEATURE_ENVIRONMENT",
"role_id": 10,
},
{
"environment": "default",
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
"role_id": 10,
},
{
"environment": "development",
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
"role_id": 10,
},
{
"environment": "production",
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
"role_id": 10,
},
{
"environment": null,
"permission": "READ_PROJECT_API_TOKEN",
"role_id": 10,
},
{
"environment": null,
"permission": "CREATE_PROJECT_API_TOKEN",
"role_id": 10,
},
{
"environment": null,
"permission": "DELETE_PROJECT_API_TOKEN",
"role_id": 10,
},
{
"environment": null,
"permission": "UPDATE_FEATURE_DEPENDENCY",
"role_id": 10,
},
]
`;

View File

@ -48,7 +48,7 @@ test('Dedupe permissions migration correctly dedupes permissions', async () => {
const client = new Client(config.db); const client = new Client(config.db);
await client.connect(); await client.connect();
await client.query(` await client.query(`
DELETE FROM "dedupe_permissions_test"."role_permission"; DELETE FROM "dedupe_permissions_test"."roles";
INSERT INTO "dedupe_permissions_test"."roles" (id, name, description, type) VALUES (101, 'Role 1', 'A test role', 'custom'); INSERT INTO "dedupe_permissions_test"."roles" (id, name, description, type) VALUES (101, 'Role 1', 'A test role', 'custom');
INSERT INTO "dedupe_permissions_test"."roles" (id, name, description, type) VALUES (102, 'Role 2', 'A test role', 'custom'); INSERT INTO "dedupe_permissions_test"."roles" (id, name, description, type) VALUES (102, 'Role 2', 'A test role', 'custom');

View File

@ -0,0 +1,158 @@
import { getDbConfig } from './helpers/database-config';
import { createTestConfig } from '../config/test-config';
import { getInstance } from 'db-migrate';
import { log } from 'db-migrate-shared';
import { Client } from 'pg';
import { IDBOption } from 'lib/types';
log.setLogLevel('error');
async function initSchema(db: IDBOption): Promise<void> {
const client = new Client(db);
await client.connect();
await client.query(`DROP SCHEMA IF EXISTS ${db.schema} CASCADE`);
await client.query(`CREATE SCHEMA IF NOT EXISTS ${db.schema}`);
await client.end();
}
test('Favor permission name over id migration correctly assigns permissions by name', async () => {
jest.setTimeout(15000);
const config = createTestConfig({
db: {
...getDbConfig(),
pool: { min: 1, max: 4 },
schema: 'favor_permission_name_over_id_test',
ssl: false,
},
});
await initSchema(config.db);
const e2e = {
...config.db,
connectionTimeoutMillis: 2000,
};
// disable Intellij/WebStorm from setting verbose CLI argument to db-migrator
process.argv = process.argv.filter((it) => !it.includes('--verbose'));
const dbm = getInstance(true, {
cwd: `${__dirname}/../../`, // relative to src/test/e2e
config: { e2e },
env: 'e2e',
});
// Run all migrations up to, and including, this one, the last one before the favor permission name over id migration
await dbm.up('20231123100052-drop-last-seen-foreign-key.js');
// Set up the test data
const client = new Client(config.db);
await client.connect();
await client.query(`
DELETE FROM "favor_permission_name_over_id_test"."roles";
INSERT INTO "favor_permission_name_over_id_test"."roles" (id, name, description, type) VALUES (101, 'Role 1', 'A test role', 'custom');
`);
await client.query(`
DELETE FROM "favor_permission_name_over_id_test"."permissions";
INSERT INTO "favor_permission_name_over_id_test"."permissions" (id, permission, display_name, type) VALUES (101, 'TEST_PERMISSION_1', 'A test permission', 'root');
INSERT INTO "favor_permission_name_over_id_test"."permissions" (id, permission, display_name, type) VALUES (102, 'TEST_PERMISSION_2', 'A test permission', 'root');
INSERT INTO "favor_permission_name_over_id_test"."permissions" (id, permission, display_name, type) VALUES (103, 'TEST_PERMISSION_3', 'A test permission', 'root');
INSERT INTO "favor_permission_name_over_id_test"."permissions" (id, permission, display_name, type) VALUES (104, 'TEST_PERMISSION_4', 'A test permission', 'root');
`);
await client.query(`
DELETE FROM "favor_permission_name_over_id_test"."role_permission";
INSERT INTO "favor_permission_name_over_id_test"."role_permission" (role_id, permission_id) VALUES (101, 101);
INSERT INTO "favor_permission_name_over_id_test"."role_permission" (role_id, permission_id) VALUES (101, 102);
INSERT INTO "favor_permission_name_over_id_test"."role_permission" (role_id, permission_id) VALUES (101, 103);
INSERT INTO "favor_permission_name_over_id_test"."role_permission" (role_id, permission_id) VALUES (101, 104);
`);
// Run the drop permissions id migration
await dbm.up('20231123155649-favor-permission-name-over-id.js');
// Check the results
const { rows: resultsPermissions } = await client.query(`
SELECT * FROM "favor_permission_name_over_id_test"."permissions" ORDER BY created_at;
`);
const { rows: resultsRolePermission } = await client.query(`
SELECT * FROM "favor_permission_name_over_id_test"."role_permission" WHERE role_id = 101 ORDER BY created_at;
`);
// We keep the id for now, but will remove it in the next major version
expect('id' in resultsPermissions[0]).toEqual(true);
expect(
resultsPermissions.map(({ permission, display_name, type }) => ({
permission,
display_name,
type,
})),
).toMatchSnapshot();
// We keep the permission_id for now, but will remove it in the next minor version
expect('permission_id' in resultsRolePermission[0]).toEqual(true);
expect('permission' in resultsRolePermission[0]).toEqual(true);
expect(resultsRolePermission.length).toEqual(4);
expect(
resultsRolePermission.map(({ role_id, permission }) => ({
role_id,
permission,
})),
).toEqual([
{ role_id: 101, permission: 'TEST_PERMISSION_1' },
{ role_id: 101, permission: 'TEST_PERMISSION_2' },
{ role_id: 101, permission: 'TEST_PERMISSION_3' },
{ role_id: 101, permission: 'TEST_PERMISSION_4' },
]);
// Check the results that ensure the default roles exist and have the correct permissions
const { rows: resultsRoles } = await client.query(`
SELECT name, description, type FROM "favor_permission_name_over_id_test"."roles" WHERE id != 101 ORDER BY created_at;
`);
const { rows: resultsDefaultRolePermissions } = await client.query(`
SELECT role_id, environment, permission FROM "favor_permission_name_over_id_test"."role_permission" WHERE role_id != 101 ORDER BY created_at;
`);
expect(resultsRoles.length).toEqual(5);
expect(resultsRoles).toEqual([
{
name: 'Admin',
description:
'Users with the root admin role have superuser access to Unleash and can perform any operation within the Unleash platform.',
type: 'root',
},
{
name: 'Editor',
description:
'Users with the root editor role have access to most features in Unleash, but can not manage users and roles in the root scope. Editors will be added as project owners when creating projects and get superuser rights within the context of these projects. Users with the editor role will also get access to most permissions on the default project by default.',
type: 'root',
},
{
name: 'Viewer',
description:
'Users with the root viewer role can only read root resources in Unleash. Viewers can be added to specific projects as project members. Users with the viewer role may not view API tokens.',
type: 'root',
},
{
name: 'Owner',
description:
'Users with the project owner role have full control over the project, and can add and manage other users within the project context, manage feature toggles within the project, and control advanced project features like archiving and deleting the project.',
type: 'project',
},
{
name: 'Member',
description:
"Users with the project member role are allowed to view, create, and update feature toggles within a project, but have limited permissions in regards to managing the project's user access and can not archive or delete the project.",
type: 'project',
},
]);
expect(resultsDefaultRolePermissions).toMatchSnapshot();
await client.end();
await dbm.reset();
});