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

feat: ensure at least one owner on remove user/group access (#5085)

## About the changes
This makes sure that projects have at least one owner, either a group or
a user. This is to prevent accidentally losing access to a project.

We check this when removing a user/group or when changing the role of a
user/group

**Note**: We can still leave a group empty as the only owner of the
project, but that's okay because we can still add more users to the
group
This commit is contained in:
Gastón Fournier 2023-10-19 14:14:59 +02:00 committed by GitHub
parent 6760fc0723
commit 3d9f31f839
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 269 additions and 94 deletions

View File

@ -14,7 +14,6 @@ import FakeGroupStore from '../../../test/fixtures/fake-group-store';
import FakeEventStore from '../../../test/fixtures/fake-event-store';
import ProjectStore from '../../db/project-store';
import FeatureToggleStore from '../feature-toggle/feature-toggle-store';
import FeatureTypeStore from '../../db/feature-type-store';
import { FeatureEnvironmentStore } from '../../db/feature-environment-store';
import ProjectStatsStore from '../../db/project-stats-store';
import {
@ -29,7 +28,6 @@ import { FavoriteFeaturesStore } from '../../db/favorite-features-store';
import { FavoriteProjectsStore } from '../../db/favorite-projects-store';
import FakeProjectStore from '../../../test/fixtures/fake-project-store';
import FakeFeatureToggleStore from '../feature-toggle/fakes/fake-feature-toggle-store';
import FakeFeatureTypeStore from '../../../test/fixtures/fake-feature-type-store';
import FakeEnvironmentStore from '../../../test/fixtures/fake-environment-store';
import FakeFeatureEnvironmentStore from '../../../test/fixtures/fake-feature-environment-store';
import FakeProjectStatsStore from '../../../test/fixtures/fake-project-stats-store';
@ -41,8 +39,6 @@ import {
createPrivateProjectChecker,
} from '../private-project/createPrivateProjectChecker';
import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store';
import { LastSeenAtReadModel } from '../../services/client-metrics/last-seen/last-seen-read-model';
import { FakeLastSeenReadModel } from '../../services/client-metrics/last-seen/fake-last-seen-read-model';
export const createProjectService = (
db: Db,
@ -63,7 +59,6 @@ export const createProjectService = (
getLogger,
flagResolver,
);
const featureTypeStore = new FeatureTypeStore(db, getLogger);
const accountStore = new AccountStore(db, getLogger);
const environmentStore = new EnvironmentStore(db, eventBus, getLogger);
const featureEnvironmentStore = new FeatureEnvironmentStore(
@ -106,14 +101,12 @@ export const createProjectService = (
);
const privateProjectChecker = createPrivateProjectChecker(db, config);
const lastSeenReadModel = new LastSeenAtReadModel(db);
return new ProjectService(
{
projectStore,
eventStore,
featureToggleStore,
featureTypeStore,
environmentStore,
featureEnvironmentStore,
accountStore,
@ -126,7 +119,6 @@ export const createProjectService = (
favoriteService,
eventService,
privateProjectChecker,
lastSeenReadModel,
);
};
@ -138,7 +130,6 @@ export const createFakeProjectService = (
const projectStore = new FakeProjectStore();
const groupStore = new FakeGroupStore();
const featureToggleStore = new FakeFeatureToggleStore();
const featureTypeStore = new FakeFeatureTypeStore();
const accountStore = new FakeAccountStore();
const environmentStore = new FakeEnvironmentStore();
const featureEnvironmentStore = new FakeFeatureEnvironmentStore();
@ -169,14 +160,12 @@ export const createFakeProjectService = (
);
const privateProjectChecker = createFakePrivateProjectChecker();
const fakeLastSeenReadModel = new FakeLastSeenReadModel();
return new ProjectService(
{
projectStore,
eventStore,
featureToggleStore,
featureTypeStore,
environmentStore,
featureEnvironmentStore,
accountStore,
@ -189,6 +178,5 @@ export const createFakeProjectService = (
favoriteService,
eventService,
privateProjectChecker,
fakeLastSeenReadModel,
);
};

View File

@ -568,7 +568,7 @@ export class AccessService {
}
async removeDefaultProjectRoles(
owner: User,
owner: IUser,
projectId: string,
): Promise<void> {
this.logger.info(`Removing project roles for ${projectId}`);

View File

@ -1,6 +1,6 @@
import { subDays } from 'date-fns';
import { ValidationError } from 'joi';
import User, { IUser } from '../types/user';
import { IUser } from '../types/user';
import { AccessService, AccessWithRoles } from './access-service';
import NameExistsError from '../error/name-exists-error';
import InvalidOperationError from '../error/invalid-operation-error';
@ -15,7 +15,6 @@ import {
IEventStore,
IFeatureEnvironmentStore,
IFeatureToggleStore,
IFeatureTypeStore,
IProject,
IProjectOverview,
IProjectWithCount,
@ -65,8 +64,6 @@ import { ProjectDoraMetricsSchema } from 'lib/openapi';
import { checkFeatureNamingData } from '../features/feature-naming-pattern/feature-naming-validation';
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
import EventService from './event-service';
import { ILastSeenReadModel } from './client-metrics/last-seen/types/last-seen-read-model-type';
import { LastSeenMapper } from './client-metrics/last-seen/last-seen-mapper';
const getCreatedBy = (user: IUser) => user.email || user.username || 'unknown';
@ -89,6 +86,10 @@ interface ICalculateStatus {
updates: IProjectStats;
}
function includes(list: number[], { id }: { id: number }): boolean {
return list.some((l) => l === id);
}
export default class ProjectService {
private projectStore: IProjectStore;
@ -98,8 +99,6 @@ export default class ProjectService {
private featureToggleStore: IFeatureToggleStore;
private featureTypeStore: IFeatureTypeStore;
private featureEnvironmentStore: IFeatureEnvironmentStore;
private environmentStore: IEnvironmentStore;
@ -120,8 +119,6 @@ export default class ProjectService {
private projectStatsStore: IProjectStatsStore;
private lastSeenReadModel: ILastSeenReadModel;
private flagResolver: IFlagResolver;
private isEnterprise: boolean;
@ -131,7 +128,6 @@ export default class ProjectService {
projectStore,
eventStore,
featureToggleStore,
featureTypeStore,
environmentStore,
featureEnvironmentStore,
accountStore,
@ -141,7 +137,6 @@ export default class ProjectService {
| 'projectStore'
| 'eventStore'
| 'featureToggleStore'
| 'featureTypeStore'
| 'environmentStore'
| 'featureEnvironmentStore'
| 'accountStore'
@ -154,7 +149,6 @@ export default class ProjectService {
favoriteService: FavoritesService,
eventService: EventService,
privateProjectChecker: IPrivateProjectChecker,
lastSeenReadModel: ILastSeenReadModel,
) {
this.projectStore = projectStore;
this.environmentStore = environmentStore;
@ -162,7 +156,6 @@ export default class ProjectService {
this.accessService = accessService;
this.eventStore = eventStore;
this.featureToggleStore = featureToggleStore;
this.featureTypeStore = featureTypeStore;
this.featureToggleService = featureToggleService;
this.favoritesService = favoriteService;
this.privateProjectChecker = privateProjectChecker;
@ -170,7 +163,6 @@ export default class ProjectService {
this.groupService = groupService;
this.eventService = eventService;
this.projectStatsStore = projectStatsStore;
this.lastSeenReadModel = lastSeenReadModel;
this.logger = config.getLogger('services/project-service.js');
this.flagResolver = config.flagResolver;
this.isEnterprise = config.isEnterprise;
@ -267,7 +259,7 @@ export default class ProjectService {
return data;
}
async updateProject(updatedProject: IProject, user: User): Promise<void> {
async updateProject(updatedProject: IProject, user: IUser): Promise<void> {
const preData = await this.projectStore.get(updatedProject.id);
await this.projectStore.update(updatedProject);
@ -283,7 +275,7 @@ export default class ProjectService {
async updateProjectEnterpriseSettings(
updatedProject: IProjectEnterpriseSettingsUpdate,
user: User,
user: IUser,
): Promise<void> {
const preData = await this.projectStore.get(updatedProject.id);
@ -330,7 +322,7 @@ export default class ProjectService {
async changeProject(
newProjectId: string,
featureName: string,
user: User,
user: IUser,
currentProjectId: string,
): Promise<any> {
const feature = await this.featureToggleStore.get(featureName);
@ -372,7 +364,7 @@ export default class ProjectService {
return updatedFeature;
}
async deleteProject(id: string, user: User): Promise<void> {
async deleteProject(id: string, user: IUser): Promise<void> {
if (id === DEFAULT_PROJECT) {
throw new InvalidOperationError(
'You can not delete the default project!',
@ -508,6 +500,11 @@ export default class ProjectService {
userId,
);
const ownerRole = await this.accessService.getRoleByName(
RoleName.OWNER,
);
await this.validateAtLeastOneOwner(projectId, ownerRole);
await this.accessService.removeUserAccess(projectId, userId);
await this.eventService.storeEvent(
@ -532,6 +529,11 @@ export default class ProjectService {
groupId,
);
const ownerRole = await this.accessService.getRoleByName(
RoleName.OWNER,
);
await this.validateAtLeastOneOwner(projectId, ownerRole);
await this.accessService.removeGroupAccess(projectId, groupId);
await this.eventService.storeEvent(
@ -598,6 +600,8 @@ export default class ProjectService {
undefined,
);
await this.validateAtLeastOneOwner(projectId, role);
await this.accessService.removeGroupFromRole(
group.id,
role.id,
@ -675,28 +679,39 @@ export default class ProjectService {
async setRolesForUser(
projectId: string,
userId: number,
roles: number[],
newRoles: number[],
createdByUserName: string,
): Promise<void> {
const existingRoles = await this.accessService.getProjectRolesForUser(
const currentRoles = await this.accessService.getProjectRolesForUser(
projectId,
userId,
);
const ownerRole = await this.accessService.getRoleByName(
RoleName.OWNER,
);
const hasOwnerRole = includes(currentRoles, ownerRole);
const isRemovingOwnerRole = !includes(newRoles, ownerRole);
if (hasOwnerRole && isRemovingOwnerRole) {
await this.validateAtLeastOneOwner(projectId, ownerRole);
}
await this.accessService.setProjectRolesForUser(
projectId,
userId,
roles,
newRoles,
);
await this.eventService.storeEvent(
new ProjectAccessUserRolesUpdated({
project: projectId,
createdBy: createdByUserName,
data: {
roles,
roles: newRoles,
userId,
},
preData: {
roles: existingRoles,
roles: currentRoles,
userId,
},
}),
@ -706,17 +721,28 @@ export default class ProjectService {
async setRolesForGroup(
projectId: string,
groupId: number,
roles: number[],
newRoles: number[],
createdBy: string,
): Promise<void> {
const existingRoles = await this.accessService.getProjectRolesForGroup(
const currentRoles = await this.accessService.getProjectRolesForGroup(
projectId,
groupId,
);
const ownerRole = await this.accessService.getRoleByName(
RoleName.OWNER,
);
const hasOwnerRole = includes(currentRoles, ownerRole);
const isRemovingOwnerRole = !includes(newRoles, ownerRole);
if (hasOwnerRole && isRemovingOwnerRole) {
await this.validateAtLeastOneOwner(projectId, ownerRole);
}
await this.validateAtLeastOneOwner(projectId, ownerRole);
await this.accessService.setProjectRolesForGroup(
projectId,
groupId,
roles,
newRoles,
createdBy,
);
await this.eventService.storeEvent(
@ -724,11 +750,11 @@ export default class ProjectService {
project: projectId,
createdBy,
data: {
roles,
roles: newRoles,
groupId,
},
preData: {
roles: existingRoles,
roles: currentRoles,
groupId,
},
}),
@ -1091,7 +1117,7 @@ export default class ProjectService {
return {
stats: projectStats,
name: project.name,
description: project.description,
description: project.description!,
mode: project.mode,
featureLimit: project.featureLimit,
featureNaming: project.featureNaming,

View File

@ -17,8 +17,10 @@ import {
createFeatureToggleService,
createProjectService,
} from '../../../lib/features';
import { IGroup, IUnleashStores } from 'lib/types';
import { User } from 'lib/server-impl';
let stores;
let stores: IUnleashStores;
let db: ITestDb;
let projectService: ProjectService;
@ -26,7 +28,8 @@ let accessService: AccessService;
let eventService: EventService;
let environmentService: EnvironmentService;
let featureToggleService: FeatureToggleService;
let user;
let user: User; // many methods in this test use User instead of IUser
let group: IGroup;
const isProjectUser = async (
userId: number,
@ -41,13 +44,17 @@ const isProjectUser = async (
beforeAll(async () => {
db = await dbInit('project_service_serial', getLogger);
stores = db.stores;
// @ts-ignore return type IUser type missing generateImageUrl
user = await stores.userStore.insert({
name: 'Some Name',
email: 'test@getunleash.io',
});
group = await stores.groupStore.create({
name: 'aTestGroup',
description: '',
});
const config = createTestConfig({
getLogger,
// @ts-ignore
experimental: {
flags: { privateProjects: true },
},
@ -164,6 +171,7 @@ test('should not be able to delete project with toggles', async () => {
await projectService.createProject(project, user);
await stores.featureToggleStore.create(project.id, {
name: 'test-project-delete',
// @ts-ignore project does not exist in type FeatureToggleDTO
project: project.id,
enabled: false,
defaultStickiness: 'default',
@ -491,31 +499,6 @@ test('should remove user from the project', async () => {
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',
mode: 'open' as const,
defaultStickiness: 'clientId',
};
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',
@ -528,6 +511,7 @@ test('should not change project if feature toggle project does not match current
const toggle = { name: 'test-toggle' };
await projectService.createProject(project, user);
// @ts-ignore user is wrong parameter type, should be string
await featureToggleService.createFeatureToggle(project.id, toggle, user);
try {
@ -555,6 +539,7 @@ test('should return 404 if no project is found with the project id', async () =>
const toggle = { name: 'test-toggle-2' };
await projectService.createProject(project, user);
// @ts-ignore user is wrong parameter type, should be string
await featureToggleService.createFeatureToggle(project.id, toggle, user);
try {
@ -594,6 +579,7 @@ test('should fail if user is not authorized', async () => {
await projectService.createProject(project, user);
await projectService.createProject(projectDestination, projectAdmin1);
// @ts-ignore user is wrong parameter type, should be string
await featureToggleService.createFeatureToggle(project.id, toggle, user);
try {
@ -626,6 +612,7 @@ test('should change project when checks pass', async () => {
await projectService.createProject(projectA, user);
await projectService.createProject(projectB, user);
// @ts-ignore user is wrong parameter type, should be string
await featureToggleService.createFeatureToggle(projectA.id, toggle, user);
await projectService.changeProject(
projectB.id,
@ -656,6 +643,7 @@ test('changing project should emit event even if user does not have a username s
const toggle = { name: randomId() };
await projectService.createProject(projectA, user);
await projectService.createProject(projectB, user);
// @ts-ignore user is wrong parameter type, should be string
await featureToggleService.createFeatureToggle(projectA.id, toggle, user);
const eventsBeforeChange = await stores.eventStore.getEvents();
await projectService.changeProject(
@ -686,6 +674,7 @@ test('should require equal project environments to move features', async () => {
await projectService.createProject(projectA, user);
await projectService.createProject(projectB, user);
// @ts-ignore user is wrong parameter type, should be string
await featureToggleService.createFeatureToggle(projectA.id, toggle, user);
await stores.environmentStore.create(environment);
await environmentService.addEnvironmentToProject(
@ -1013,40 +1002,180 @@ test('should able to assign role without existing members', async () => {
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',
mode: 'open' as const,
defaultStickiness: 'clientId',
};
await projectService.createProject(project, user);
describe('ensure project has at least one owner', () => {
test('should not remove user from the project', async () => {
const project = {
id: 'remove-users-not-allowed',
name: 'New project',
description: 'Blah',
mode: 'open' as const,
defaultStickiness: 'clientId',
};
await projectService.createProject(project, user);
const projectMember1 = await stores.userStore.insert({
name: 'Some Member',
email: 'update991@getunleash.io',
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'),
);
await expect(async () => {
await projectService.removeUserAccess(project.id, user.id, 'test');
}).rejects.toThrowError(
new Error('A project must have at least one owner'),
);
});
const memberRole = await stores.roleStore.getRoleByName(RoleName.MEMBER);
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',
mode: 'open' as const,
defaultStickiness: 'clientId',
};
await projectService.createProject(project, user);
await projectService.addUser(
project.id,
memberRole.id,
projectMember1.id,
'test',
);
const projectMember1 = await stores.userStore.insert({
name: 'Some Member',
email: 'update991@getunleash.io',
});
await expect(async () => {
await projectService.changeRole(
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'),
);
await expect(async () => {
await projectService.setRolesForUser(
project.id,
user.id,
[memberRole.id],
'test',
);
}).rejects.toThrowError(
new Error('A project must have at least one owner'),
);
});
async function projectWithGroupOwner(projectId: string) {
const project = {
id: projectId,
name: 'New project',
description: 'Blah',
mode: 'open' as const,
defaultStickiness: 'clientId',
};
await projectService.createProject(project, user);
const roles = await stores.roleStore.getRolesForProject(project.id);
const ownerRole = roles.find((r) => r.name === RoleName.OWNER)!;
await projectService.addGroup(
project.id,
ownerRole.id,
group.id,
'test',
);
// this should be fine, leaving the group as the only owner
// note group has zero members, but it still acts as an owner
await projectService.removeUser(
project.id,
ownerRole.id,
user.id,
'test',
);
}).rejects.toThrowError(
new Error('A project must have at least one owner'),
);
return {
project,
group,
ownerRole,
};
}
test('should not remove group from the project', async () => {
const { project, group, ownerRole } = await projectWithGroupOwner(
'remove-group-not-allowed',
);
await expect(async () => {
await projectService.removeGroup(
project.id,
ownerRole.id,
group.id,
'test',
);
}).rejects.toThrowError(
new Error('A project must have at least one owner'),
);
await expect(async () => {
await projectService.removeGroupAccess(
project.id,
group.id,
'test',
);
}).rejects.toThrowError(
new Error('A project must have at least one owner'),
);
});
test('should not update role for group on project when she is the owner', async () => {
const { project, group } = await projectWithGroupOwner(
'update-group-not-allowed',
);
const memberRole = await stores.roleStore.getRoleByName(
RoleName.MEMBER,
);
await expect(async () => {
await projectService.changeGroupRole(
project.id,
memberRole.id,
group.id,
'test',
);
}).rejects.toThrowError(
new Error('A project must have at least one owner'),
);
await expect(async () => {
await projectService.setRolesForGroup(
project.id,
group.id,
[memberRole.id],
'test',
);
}).rejects.toThrowError(
new Error('A project must have at least one owner'),
);
});
});
test('Should allow bulk update of group permissions', async () => {
@ -1056,6 +1185,7 @@ test('Should allow bulk update of group permissions', async () => {
mode: 'open' as const,
defaultStickiness: 'clientId',
};
// @ts-ignore user.id is wrong type should be user
await projectService.createProject(project, user.id);
const groupStore = stores.groupStore;
@ -1124,6 +1254,7 @@ test('Should allow bulk update of only groups', async () => {
};
const groupStore = stores.groupStore;
// @ts-ignore user.id is wrong type should be user
await projectService.createProject(project, user.id);
const group1 = await groupStore.create({
@ -1158,6 +1289,7 @@ test('Should allow permutations of roles, groups and users when adding a new acc
defaultStickiness: 'clientId',
};
// @ts-ignore user.id is wrong type should be user
await projectService.createProject(project, user.id);
const group1 = await stores.groupStore.create({
@ -1232,11 +1364,13 @@ test('should only count active feature toggles for project', async () => {
await stores.featureToggleStore.create(project.id, {
name: 'only-active-t1',
// @ts-ignore project property does not exist in FeatureToggleDTO
project: project.id,
enabled: false,
});
await stores.featureToggleStore.create(project.id, {
name: 'only-active-t2',
// @ts-ignore project property does not exist in FeatureToggleDTO
project: project.id,
enabled: false,
});
@ -1261,6 +1395,7 @@ test('should list projects with all features archived', async () => {
await stores.featureToggleStore.create(project.id, {
name: 'archived-toggle',
// @ts-ignore project property does not exist in FeatureToggleDTO
project: project.id,
enabled: false,
});
@ -1294,6 +1429,7 @@ test('should calculate average time to production', async () => {
defaultStickiness: 'clientId',
};
// @ts-ignore user.id is wrong type should be user
await projectService.createProject(project, user.id);
const toggles = [
@ -1309,6 +1445,7 @@ test('should calculate average time to production', async () => {
return featureToggleService.createFeatureToggle(
project.id,
toggle,
// @ts-ignore user is wrong parameter type, should be string
user,
);
}),
@ -1360,6 +1497,7 @@ test('should calculate average time to production ignoring some items', async ()
tags: [],
});
// @ts-ignore user.id is wrong type should be user
await projectService.createProject(project, user.id);
await stores.environmentStore.create({
name: 'customEnv',
@ -1369,6 +1507,7 @@ test('should calculate average time to production ignoring some items', async ()
// actual toggle we take for calculations
const toggle = { name: 'main-toggle' };
// @ts-ignore user is wrong parameter type, should be string
await featureToggleService.createFeatureToggle(project.id, toggle, user);
await updateFeature(toggle.name, {
created_at: subDays(new Date(), 20),
@ -1384,6 +1523,7 @@ test('should calculate average time to production ignoring some items', async ()
// ignore toggles enabled in non-prod envs
const devToggle = { name: 'dev-toggle' };
// @ts-ignore user is wrong parameter type, should be string
await featureToggleService.createFeatureToggle(project.id, devToggle, user);
await eventService.storeEvent(
new FeatureEnvironmentEvent({
@ -1397,6 +1537,7 @@ test('should calculate average time to production ignoring some items', async ()
await featureToggleService.createFeatureToggle(
'default',
otherProjectToggle,
// @ts-ignore user is wrong parameter type, should be string
user,
);
await eventService.storeEvent(
@ -1408,6 +1549,7 @@ test('should calculate average time to production ignoring some items', async ()
await featureToggleService.createFeatureToggle(
project.id,
nonReleaseToggle,
// @ts-ignore user is wrong parameter type, should be string
user,
);
await eventService.storeEvent(
@ -1419,6 +1561,7 @@ test('should calculate average time to production ignoring some items', async ()
await featureToggleService.createFeatureToggle(
project.id,
previouslyDeleteToggle,
// @ts-ignore user is wrong parameter type, should be string
user,
);
await eventService.storeEvent(
@ -1441,6 +1584,7 @@ test('should get correct amount of features created in current and past window',
defaultStickiness: 'clientId',
};
// @ts-ignore user.id is wrong type should be user
await projectService.createProject(project, user.id);
const toggles = [
@ -1455,6 +1599,7 @@ test('should get correct amount of features created in current and past window',
return featureToggleService.createFeatureToggle(
project.id,
toggle,
// @ts-ignore user is wrong parameter type
user,
);
}),
@ -1478,6 +1623,7 @@ test('should get correct amount of features archived in current and past window'
defaultStickiness: 'clientId',
};
// @ts-ignore user.id is wrong parameter type, should be user
await projectService.createProject(project, user.id);
const toggles = [
@ -1492,6 +1638,7 @@ test('should get correct amount of features archived in current and past window'
return featureToggleService.createFeatureToggle(
project.id,
toggle,
// @ts-ignore user is wrong parameter type, should be string
user,
);
}),
@ -1529,6 +1676,7 @@ test('should get correct amount of project members for current and past window',
defaultStickiness: 'default',
};
// @ts-ignore user.id is wrong type should be user
await projectService.createProject(project, user.id);
const users = [
@ -1569,6 +1717,7 @@ test('should return average time to production per toggle', async () => {
defaultStickiness: 'clientId',
};
// @ts-ignore user.id is wrong type should be user
await projectService.createProject(project, user.id);
const toggles = [
@ -1584,7 +1733,7 @@ test('should return average time to production per toggle', async () => {
return featureToggleService.createFeatureToggle(
project.id,
toggle,
user,
user.email!,
);
}),
);
@ -1633,7 +1782,9 @@ test('should return average time to production per toggle for a specific project
defaultStickiness: 'clientId',
};
// @ts-ignore user.id is wrong parameter type, should be user
await projectService.createProject(project1, user.id);
// @ts-ignore user.id is wrong parameter type, should be user
await projectService.createProject(project2, user.id);
const togglesProject1 = [
@ -1652,6 +1803,7 @@ test('should return average time to production per toggle for a specific project
return featureToggleService.createFeatureToggle(
project1.id,
toggle,
// @ts-ignore user is wrong parameter type, should be string
user,
);
}),
@ -1662,6 +1814,7 @@ test('should return average time to production per toggle for a specific project
return featureToggleService.createFeatureToggle(
project2.id,
toggle,
// @ts-ignore user is wrong parameter type, should be string
user,
);
}),
@ -1726,6 +1879,7 @@ test('should return average time to production per toggle and include archived t
defaultStickiness: 'clientId',
};
// @ts-ignore user.id is wrong parameter type, should be user
await projectService.createProject(project1, user.id);
const togglesProject1 = [
@ -1739,6 +1893,7 @@ test('should return average time to production per toggle and include archived t
return featureToggleService.createFeatureToggle(
project1.id,
toggle,
// @ts-ignore user is wrong parameter type, should be string
user,
);
}),
@ -1790,6 +1945,7 @@ describe('feature flag naming patterns', () => {
featureNaming,
};
// @ts-ignore user.id is wrong parameter type, should be user
await projectService.createProject(project, user.id);
await projectService.updateProjectEnterpriseSettings(project, user);
@ -1804,6 +1960,7 @@ describe('feature flag naming patterns', () => {
...project,
featureNaming: { pattern: newPattern },
},
// @ts-ignore user.id is wrong parameter type, should be user
user.id,
);
@ -1822,10 +1979,12 @@ test('deleting a project with archived toggles should result in any remaining ar
};
const toggleName = 'archived-and-deleted';
// @ts-ignore user.id is wrong parameter type, should be user
await projectService.createProject(project, user.id);
await stores.featureToggleStore.create(project.id, {
name: toggleName,
// @ts-ignore project property does not exist in FeatureToggleDTO
project: project.id,
enabled: false,
defaultStickiness: 'default',
@ -1836,6 +1995,7 @@ test('deleting a project with archived toggles should result in any remaining ar
// bring the project back again, previously this would allow those archived toggles to be resurrected
// we now expect them to be deleted correctly
// @ts-ignore user.id is wrong parameter type, should be user
await projectService.createProject(project, user.id);
const toggles = await stores.featureToggleStore.getAll({
@ -1852,6 +2012,7 @@ test('deleting a project with no archived toggles should not result in an error'
name: 'project-with-nothing',
};
// @ts-ignore user.id is wrong parameter type, should be user
await projectService.createProject(project, user.id);
await projectService.deleteProject(project.id, user);
});