From 3d9f31f8396ce49da9d005d4b9d7961aabb5440e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Thu, 19 Oct 2023 14:14:59 +0200 Subject: [PATCH] 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 --- .../features/project/createProjectService.ts | 12 - src/lib/services/access-service.ts | 2 +- src/lib/services/project-service.ts | 82 ++++-- .../e2e/services/project-service.e2e.test.ts | 267 ++++++++++++++---- 4 files changed, 269 insertions(+), 94 deletions(-) diff --git a/src/lib/features/project/createProjectService.ts b/src/lib/features/project/createProjectService.ts index fbafe4ae357b..3472c526b6db 100644 --- a/src/lib/features/project/createProjectService.ts +++ b/src/lib/features/project/createProjectService.ts @@ -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, ); }; diff --git a/src/lib/services/access-service.ts b/src/lib/services/access-service.ts index 812aef1b08fc..f7e55a4a5a78 100644 --- a/src/lib/services/access-service.ts +++ b/src/lib/services/access-service.ts @@ -568,7 +568,7 @@ export class AccessService { } async removeDefaultProjectRoles( - owner: User, + owner: IUser, projectId: string, ): Promise { this.logger.info(`Removing project roles for ${projectId}`); diff --git a/src/lib/services/project-service.ts b/src/lib/services/project-service.ts index 81a544173222..961b20c71f1d 100644 --- a/src/lib/services/project-service.ts +++ b/src/lib/services/project-service.ts @@ -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 { + async updateProject(updatedProject: IProject, user: IUser): Promise { 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 { 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 { const feature = await this.featureToggleStore.get(featureName); @@ -372,7 +364,7 @@ export default class ProjectService { return updatedFeature; } - async deleteProject(id: string, user: User): Promise { + async deleteProject(id: string, user: IUser): Promise { 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 { - 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 { - 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, diff --git a/src/test/e2e/services/project-service.e2e.test.ts b/src/test/e2e/services/project-service.e2e.test.ts index 281a35024497..ab5187aec348 100644 --- a/src/test/e2e/services/project-service.e2e.test.ts +++ b/src/test/e2e/services/project-service.e2e.test.ts @@ -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', + }); + + const memberRole = await stores.roleStore.getRoleByName( + RoleName.MEMBER, + ); - await expect(async () => { - await projectService.changeRole( + 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); });