1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-04 01:18:20 +02:00
unleash.unleash/src/test/e2e/services/project-service.e2e.test.ts
2022-11-29 16:06:08 +01:00

1024 lines
29 KiB
TypeScript

import dbInit, { ITestDb } from '../helpers/database-init';
import getLogger from '../../fixtures/no-logger';
import FeatureToggleService from '../../../lib/services/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 { createTestConfig } from '../../config/test-config';
import { RoleName } from '../../../lib/types/model';
import { randomId } from '../../../lib/util/random-id';
import EnvironmentService from '../../../lib/services/environment-service';
import IncompatibleProjectError from '../../../lib/error/incompatible-project-error';
import { SegmentService } from '../../../lib/services/segment-service';
import { GroupService } from '../../../lib/services/group-service';
let stores;
let db: ITestDb;
let projectService: ProjectService;
let groupService: GroupService;
let accessService: AccessService;
let environmentService: EnvironmentService;
let featureToggleService: FeatureToggleService;
let user;
beforeAll(async () => {
db = await dbInit('project_service_serial', getLogger);
stores = db.stores;
user = await stores.userStore.insert({
name: 'Some Name',
email: 'test@getunleash.io',
});
const config = createTestConfig({
getLogger,
// @ts-ignore
experimental: { environments: { enabled: true } },
});
groupService = new GroupService(stores, config);
accessService = new AccessService(stores, config, groupService);
featureToggleService = new FeatureToggleService(
stores,
config,
new SegmentService(stores, config),
accessService,
);
environmentService = new EnvironmentService(stores, config);
projectService = new ProjectService(
stores,
config,
accessService,
featureToggleService,
groupService,
);
});
afterAll(async () => {
await db.destroy();
});
afterEach(async () => {
const envs = await stores.environmentStore.getAll();
const deleteEnvs = envs
.filter((env) => env.name !== 'default')
.map(async (env) => {
await stores.environmentStore.delete(env.name);
});
const users = await stores.userStore.getAll();
const wipeUserPermissions = users.map(async (u) => {
await stores.accessStore.unlinkUserRoles(u.id);
});
await Promise.allSettled(deleteEnvs);
await Promise.allSettled(wipeUserPermissions);
});
test('should have default project', async () => {
const project = await projectService.getProject('default');
expect(project).toBeDefined();
expect(project.id).toBe('default');
});
test('should list all projects', async () => {
const project = {
id: 'test-list',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const projects = await projectService.getProjects();
expect(projects).toHaveLength(2);
expect(projects.find((p) => p.name === project.name)?.memberCount).toBe(1);
});
test('should create new project', async () => {
const project = {
id: 'test',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const ret = await projectService.getProject('test');
expect(project.id).toEqual(ret.id);
expect(project.name).toEqual(ret.name);
expect(project.description).toEqual(ret.description);
expect(ret.createdAt).toBeTruthy();
});
test('should delete project', async () => {
const project = {
id: 'test-delete',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
await projectService.deleteProject(project.id, user);
try {
await projectService.getProject(project.id);
} catch (err) {
expect(err.message).toBe('No project found');
}
});
test('should not be able to delete project with toggles', async () => {
const project = {
id: 'test-delete-with-toggles',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
await stores.featureToggleStore.create(project.id, {
name: 'test-project-delete',
project: project.id,
enabled: false,
});
try {
await projectService.deleteProject(project.id, user);
} catch (err) {
expect(err.message).toBe(
'You can not delete a project with active feature toggles',
);
}
});
test('should not delete "default" project', async () => {
try {
await projectService.deleteProject('default', user);
} catch (err) {
expect(err.message).toBe('You can not delete the default project!');
}
});
test('should validate name, legal', async () => {
const result = await projectService.validateId('new_name');
expect(result).toBe(true);
});
test('should not be able to create existing project', async () => {
const project = {
id: 'test-delete',
name: 'New project',
description: 'Blah',
};
try {
await projectService.createProject(project, user);
await projectService.createProject(project, user);
} catch (err) {
expect(err.message).toBe('A project with this id already exists.');
}
});
test('should require URL friendly ID', async () => {
try {
await projectService.validateId('new name øæå');
} catch (err) {
expect(err.message).toBe('"value" must be URL friendly');
}
});
test('should require unique ID', async () => {
try {
await projectService.validateId('default');
} catch (err) {
expect(err.message).toBe('A project with this id already exists.');
}
});
test('should update project', async () => {
const project = {
id: 'test-update',
name: 'New project',
description: 'Blah',
};
const updatedProject = {
id: 'test-update',
name: 'New name',
description: 'Blah longer desc',
};
await projectService.createProject(project, user);
await projectService.updateProject(updatedProject, user);
const readProject = await projectService.getProject(project.id);
expect(updatedProject.name).toBe(readProject.name);
expect(updatedProject.description).toBe(readProject.description);
});
test('should give error when getting unknown project', async () => {
try {
await projectService.getProject('unknown');
} catch (err) {
expect(err.message).toBe('No project found');
}
});
test('should get list of users with access to project', async () => {
const project = {
id: 'test-roles-access',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const { users } = await projectService.getAccessToProject(project.id);
const member = await stores.roleStore.getRoleByName(RoleName.MEMBER);
const owner = await stores.roleStore.getRoleByName(RoleName.OWNER);
expect(users).toHaveLength(1);
expect(users[0].id).toBe(user.id);
expect(users[0].name).toBe(user.name);
expect(users[0].roleId).toBe(owner.id);
expect(member).toBeTruthy();
});
test('should add a member user to the project', async () => {
const project = {
id: 'add-users',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const projectMember1 = await stores.userStore.insert({
name: 'Some Member',
email: 'member1@getunleash.io',
});
const projectMember2 = await stores.userStore.insert({
name: 'Some Member 2',
email: 'member2@getunleash.io',
});
const memberRole = await stores.roleStore.getRoleByName(RoleName.MEMBER);
await projectService.addUser(
project.id,
memberRole.id,
projectMember1.id,
'test',
);
await projectService.addUser(
project.id,
memberRole.id,
projectMember2.id,
'test',
);
const { users } = await projectService.getAccessToProject(project.id);
const memberUsers = users.filter((u) => u.roleId === memberRole.id);
expect(memberUsers).toHaveLength(2);
expect(memberUsers[0].id).toBe(projectMember1.id);
expect(memberUsers[0].name).toBe(projectMember1.name);
expect(memberUsers[1].id).toBe(projectMember2.id);
expect(memberUsers[1].name).toBe(projectMember2.name);
});
test('should add admin users to the project', async () => {
const project = {
id: 'add-admin-users',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const projectAdmin1 = await stores.userStore.insert({
name: 'Some Member',
email: 'admin1@getunleash.io',
});
const projectAdmin2 = await stores.userStore.insert({
name: 'Some Member 2',
email: 'admin2@getunleash.io',
});
const ownerRole = await stores.roleStore.getRoleByName(RoleName.OWNER);
await projectService.addUser(
project.id,
ownerRole.id,
projectAdmin1.id,
'test',
);
await projectService.addUser(
project.id,
ownerRole.id,
projectAdmin2.id,
'test',
);
const { users } = await projectService.getAccessToProject(project.id);
const adminUsers = users.filter((u) => u.roleId === ownerRole.id);
expect(adminUsers).toHaveLength(3);
expect(adminUsers[1].id).toBe(projectAdmin1.id);
expect(adminUsers[1].name).toBe(projectAdmin1.name);
expect(adminUsers[2].id).toBe(projectAdmin2.id);
expect(adminUsers[2].name).toBe(projectAdmin2.name);
});
test('add user should fail if user already have access', async () => {
const project = {
id: 'add-users-twice',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const projectMember1 = await stores.userStore.insert({
name: 'Some Member',
email: 'member42@getunleash.io',
});
const memberRole = await stores.roleStore.getRoleByName(RoleName.MEMBER);
await projectService.addUser(
project.id,
memberRole.id,
projectMember1.id,
'test',
);
await expect(async () =>
projectService.addUser(
project.id,
memberRole.id,
projectMember1.id,
'test',
),
).rejects.toThrow(
new Error('User already has access to project=add-users-twice'),
);
});
test('should remove user from the project', async () => {
const project = {
id: 'remove-users',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const projectMember1 = await stores.userStore.insert({
name: 'Some Member',
email: 'member99@getunleash.io',
});
const memberRole = await stores.roleStore.getRoleByName(RoleName.MEMBER);
await projectService.addUser(
project.id,
memberRole.id,
projectMember1.id,
'test',
);
await projectService.removeUser(
project.id,
memberRole.id,
projectMember1.id,
'test',
);
const { users } = await projectService.getAccessToProject(project.id);
const memberUsers = users.filter((u) => u.roleId === memberRole.id);
expect(memberUsers).toHaveLength(0);
});
test('should not remove user from the project', async () => {
const project = {
id: 'remove-users-not-allowed',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const roles = await stores.roleStore.getRolesForProject(project.id);
const ownerRole = roles.find((r) => r.name === RoleName.OWNER);
await expect(async () => {
await projectService.removeUser(
project.id,
ownerRole.id,
user.id,
'test',
);
}).rejects.toThrowError(
new Error('A project must have at least one owner'),
);
});
test('should not change project if feature toggle project does not match current project id', async () => {
const project = {
id: 'test-change-project',
name: 'New project',
description: 'Blah',
};
const toggle = { name: 'test-toggle' };
await projectService.createProject(project, user);
await featureToggleService.createFeatureToggle(project.id, toggle, user);
try {
await projectService.changeProject(
'newProject',
toggle.name,
user,
'wrong-project-id',
);
} catch (err) {
expect(err.message).toBe(
`You need permission=${MOVE_FEATURE_TOGGLE} to perform this action`,
);
}
});
test('should return 404 if no project is found with the project id', async () => {
const project = {
id: 'test-change-project-2',
name: 'New project',
description: 'Blah',
};
const toggle = { name: 'test-toggle-2' };
await projectService.createProject(project, user);
await featureToggleService.createFeatureToggle(project.id, toggle, user);
try {
await projectService.changeProject(
'newProject',
toggle.name,
user,
project.id,
);
} catch (err) {
expect(err.message).toBe(`No project found`);
}
});
test('should fail if user is not authorized', async () => {
const project = {
id: 'test-change-project-3',
name: 'New project',
description: 'Blah',
};
const projectDestination = {
id: 'test-change-project-dest',
name: 'New project 2',
description: 'Blah',
};
const toggle = { name: 'test-toggle-3' };
const projectAdmin1 = await stores.userStore.insert({
name: 'test-change-project-creator',
email: 'admin-change-project@getunleash.io',
});
await projectService.createProject(project, user);
await projectService.createProject(projectDestination, projectAdmin1);
await featureToggleService.createFeatureToggle(project.id, toggle, user);
try {
await projectService.changeProject(
projectDestination.id,
toggle.name,
user,
project.id,
);
} catch (err) {
expect(err.message).toBe(
`You need permission=${MOVE_FEATURE_TOGGLE} to perform this action`,
);
}
});
test('should change project when checks pass', async () => {
const projectA = { id: randomId(), name: randomId() };
const projectB = { id: randomId(), name: randomId() };
const toggle = { name: randomId() };
await projectService.createProject(projectA, user);
await projectService.createProject(projectB, user);
await featureToggleService.createFeatureToggle(projectA.id, toggle, user);
await projectService.changeProject(
projectB.id,
toggle.name,
user,
projectA.id,
);
const updatedFeature = await featureToggleService.getFeature({
featureName: toggle.name,
});
expect(updatedFeature.project).toBe(projectB.id);
});
test('changing project should emit event even if user does not have a username set', async () => {
const projectA = { id: randomId(), name: randomId() };
const projectB = { id: randomId(), name: randomId() };
const toggle = { name: randomId() };
await projectService.createProject(projectA, user);
await projectService.createProject(projectB, user);
await featureToggleService.createFeatureToggle(projectA.id, toggle, user);
const eventsBeforeChange = await stores.eventStore.getEvents();
await projectService.changeProject(
projectB.id,
toggle.name,
user,
projectA.id,
);
const eventsAfterChange = await stores.eventStore.getEvents();
expect(eventsAfterChange.length).toBe(eventsBeforeChange.length + 1);
}, 10000);
test('should require equal project environments to move features', async () => {
const projectA = { id: randomId(), name: randomId() };
const projectB = { id: randomId(), name: randomId() };
const environment = { name: randomId(), type: 'production' };
const toggle = { name: randomId() };
await projectService.createProject(projectA, user);
await projectService.createProject(projectB, user);
await featureToggleService.createFeatureToggle(projectA.id, toggle, user);
await stores.environmentStore.create(environment);
await environmentService.addEnvironmentToProject(
environment.name,
projectB.id,
);
await expect(() =>
projectService.changeProject(
projectB.id,
toggle.name,
user,
projectA.id,
),
).rejects.toThrowError(IncompatibleProjectError);
});
test('A newly created project only gets connected to enabled environments', async () => {
const project = {
id: 'environment-test',
name: 'New environment project',
description: 'Blah',
};
const enabledEnv = 'connection_test';
await db.stores.environmentStore.create({
name: enabledEnv,
type: 'test',
});
const disabledEnv = 'do_not_connect';
await db.stores.environmentStore.create({
name: disabledEnv,
type: 'test',
enabled: false,
});
await projectService.createProject(project, user);
const connectedEnvs =
await db.stores.projectStore.getEnvironmentsForProject(project.id);
expect(connectedEnvs).toHaveLength(2); // default, connection_test
expect(connectedEnvs.some((e) => e === enabledEnv)).toBeTruthy();
expect(connectedEnvs.some((e) => e === disabledEnv)).toBeFalsy();
});
test('should have environments sorted in order', async () => {
const project = {
id: 'environment-order-test',
name: 'Environment testing project',
description: '',
};
const first = 'test';
const second = 'abc';
const third = 'example';
const fourth = 'mock';
await db.stores.environmentStore.create({
name: first,
type: 'test',
sortOrder: 1,
});
await db.stores.environmentStore.create({
name: fourth,
type: 'test',
sortOrder: 4,
});
await db.stores.environmentStore.create({
name: third,
type: 'test',
sortOrder: 3,
});
await db.stores.environmentStore.create({
name: second,
type: 'test',
sortOrder: 2,
});
await projectService.createProject(project, user);
const connectedEnvs =
await db.stores.projectStore.getEnvironmentsForProject(project.id);
expect(connectedEnvs).toEqual(['default', first, second, third, fourth]);
});
test('should add a user to the project with a custom role', async () => {
const project = {
id: 'add-users-custom-role',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const projectMember1 = await stores.userStore.insert({
name: 'Custom',
email: 'custom@getunleash.io',
});
const customRole = await accessService.createRole({
name: 'Service Engineer2',
description: '',
permissions: [
{
id: 2,
name: 'CREATE_FEATURE',
environment: null,
displayName: 'Create Feature Toggles',
type: 'project',
},
{
id: 8,
name: 'DELETE_FEATURE',
environment: null,
displayName: 'Delete Feature Toggles',
type: 'project',
},
],
});
await projectService.addUser(
project.id,
customRole.id,
projectMember1.id,
'test',
);
const { users } = await projectService.getAccessToProject(project.id);
const customRoleMember = users.filter((u) => u.roleId === customRole.id);
expect(customRoleMember).toHaveLength(1);
expect(customRoleMember[0].id).toBe(projectMember1.id);
expect(customRoleMember[0].name).toBe(projectMember1.name);
});
test('should delete role entries when deleting project', async () => {
const project = {
id: 'test-delete-users-1',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const user1 = await stores.userStore.insert({
name: 'Projectuser1',
email: 'project1@getunleash.io',
});
const user2 = await stores.userStore.insert({
name: 'Projectuser2',
email: 'project2@getunleash.io',
});
const customRole = await accessService.createRole({
name: 'Service Engineer',
description: '',
permissions: [
{
id: 2,
name: 'CREATE_FEATURE',
environment: null,
displayName: 'Create Feature Toggles',
type: 'project',
},
{
id: 8,
name: 'DELETE_FEATURE',
environment: null,
displayName: 'Delete Feature Toggles',
type: 'project',
},
],
});
await projectService.addUser(project.id, customRole.id, user1.id, 'test');
await projectService.addUser(project.id, customRole.id, user2.id, 'test');
let usersForRole = await accessService.getUsersForRole(customRole.id);
expect(usersForRole.length).toBe(2);
await projectService.deleteProject(project.id, user);
usersForRole = await accessService.getUsersForRole(customRole.id);
expect(usersForRole.length).toBe(0);
});
test('should change a users role in the project', async () => {
const project = {
id: 'test-change-user-role',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const projectUser = await stores.userStore.insert({
name: 'Projectuser3',
email: 'project3@getunleash.io',
});
const customRole = await accessService.createRole({
name: 'Service Engineer3',
description: '',
permissions: [
{
id: 2,
name: 'CREATE_FEATURE',
environment: null,
displayName: 'Create Feature Toggles',
type: 'project',
},
{
id: 8,
name: 'DELETE_FEATURE',
environment: null,
displayName: 'Delete Feature Toggles',
type: 'project',
},
],
});
const member = await stores.roleStore.getRoleByName(RoleName.MEMBER);
await projectService.addUser(project.id, member.id, projectUser.id, 'test');
const { users } = await projectService.getAccessToProject(project.id);
let memberUser = users.filter((u) => u.roleId === member.id);
expect(memberUser).toHaveLength(1);
expect(memberUser[0].id).toBe(projectUser.id);
expect(memberUser[0].name).toBe(projectUser.name);
await projectService.removeUser(
project.id,
member.id,
projectUser.id,
'test',
);
await projectService.addUser(
project.id,
customRole.id,
projectUser.id,
'test',
);
let { users: updatedUsers } = await projectService.getAccessToProject(
project.id,
);
const customUser = updatedUsers.filter((u) => u.roleId === customRole.id);
expect(customUser).toHaveLength(1);
expect(customUser[0].id).toBe(projectUser.id);
expect(customUser[0].name).toBe(projectUser.name);
});
test('should update role for user on project', async () => {
const project = {
id: 'update-users',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const projectMember1 = await stores.userStore.insert({
name: 'Some Member',
email: 'update99@getunleash.io',
});
const memberRole = await stores.roleStore.getRoleByName(RoleName.MEMBER);
const ownerRole = await stores.roleStore.getRoleByName(RoleName.OWNER);
await projectService.addUser(
project.id,
memberRole.id,
projectMember1.id,
'test',
);
await projectService.changeRole(
project.id,
ownerRole.id,
projectMember1.id,
'test',
);
const { users } = await projectService.getAccessToProject(project.id);
const memberUsers = users.filter((u) => u.roleId === memberRole.id);
const ownerUsers = users.filter((u) => u.roleId === ownerRole.id);
expect(memberUsers).toHaveLength(0);
expect(ownerUsers).toHaveLength(2);
});
test('should able to assign role without existing members', async () => {
const project = {
id: 'update-users-test',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const projectMember1 = await stores.userStore.insert({
name: 'Some Member',
email: 'update1999@getunleash.io',
});
const testRole = await stores.roleStore.create({
name: 'Power user',
roleType: 'custom',
description: 'Grants access to modify all environments',
});
const memberRole = await stores.roleStore.getRoleByName(RoleName.MEMBER);
await projectService.addUser(
project.id,
memberRole.id,
projectMember1.id,
'test',
);
await projectService.changeRole(
project.id,
testRole.id,
projectMember1.id,
'test',
);
const { users } = await projectService.getAccessToProject(project.id);
const memberUsers = users.filter((u) => u.roleId === memberRole.id);
const testUsers = users.filter((u) => u.roleId === testRole.id);
expect(memberUsers).toHaveLength(0);
expect(testUsers).toHaveLength(1);
});
test('should not update role for user on project when she is the owner', async () => {
const project = {
id: 'update-users-not-allowed',
name: 'New project',
description: 'Blah',
};
await projectService.createProject(project, user);
const projectMember1 = await stores.userStore.insert({
name: 'Some Member',
email: 'update991@getunleash.io',
});
const memberRole = await stores.roleStore.getRoleByName(RoleName.MEMBER);
await projectService.addUser(
project.id,
memberRole.id,
projectMember1.id,
'test',
);
await expect(async () => {
await projectService.changeRole(
project.id,
memberRole.id,
user.id,
'test',
);
}).rejects.toThrowError(
new Error('A project must have at least one owner'),
);
});
test('Should allow bulk update of group permissions', async () => {
const project = {
id: 'bulk-update-project',
name: 'bulk-update-project',
};
await projectService.createProject(project, user.id);
const groupStore = stores.groupStore;
const user1 = await stores.userStore.insert({
name: 'Vanessa Viewer',
email: 'vanv@getunleash.io',
});
const group1 = await groupStore.create({
name: 'ViewersOnly',
description: '',
});
const createFeatureRole = await accessService.createRole({
name: 'CreateRole',
description: '',
permissions: [
{
id: 2,
name: 'CREATE_FEATURE',
environment: null,
displayName: 'Create Feature Toggles',
type: 'project',
},
],
});
await projectService.addAccess(
project.id,
createFeatureRole.id,
{
users: [{ id: user1.id }],
groups: [{ id: group1.id }],
},
'some-admin-user',
);
});
test('Should bulk update of only users', async () => {
const project = 'bulk-update-project-users';
const user1 = await stores.userStore.insert({
name: 'Van Viewer',
email: 'vv@getunleash.io',
});
const createFeatureRole = await accessService.createRole({
name: 'CreateRoleForUsers',
description: '',
permissions: [
{
id: 2,
name: 'CREATE_FEATURE',
environment: null,
displayName: 'Create Feature Toggles',
type: 'project',
},
],
});
await projectService.addAccess(
project,
createFeatureRole.id,
{
users: [{ id: user1.id }],
groups: [],
},
'some-admin-user',
);
});
test('Should allow bulk update of only groups', async () => {
const project = {
id: 'bulk-update-project-only',
name: 'bulk-update-project-only',
};
const groupStore = stores.groupStore;
await projectService.createProject(project, user.id);
const group1 = await groupStore.create({
name: 'ViewersOnly',
description: '',
});
const createFeatureRole = await accessService.createRole({
name: 'CreateRoleForGroups',
description: '',
permissions: [
{
id: 2,
name: 'CREATE_FEATURE',
environment: null,
displayName: 'Create Feature Toggles',
type: 'project',
},
],
});
await projectService.addAccess(
project.id,
createFeatureRole.id,
{
users: [],
groups: [{ id: group1.id }],
},
'some-admin-user',
);
});