mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-03 01:18:43 +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:
parent
6760fc0723
commit
3d9f31f839
@ -14,7 +14,6 @@ import FakeGroupStore from '../../../test/fixtures/fake-group-store';
|
|||||||
import FakeEventStore from '../../../test/fixtures/fake-event-store';
|
import FakeEventStore from '../../../test/fixtures/fake-event-store';
|
||||||
import ProjectStore from '../../db/project-store';
|
import ProjectStore from '../../db/project-store';
|
||||||
import FeatureToggleStore from '../feature-toggle/feature-toggle-store';
|
import FeatureToggleStore from '../feature-toggle/feature-toggle-store';
|
||||||
import FeatureTypeStore from '../../db/feature-type-store';
|
|
||||||
import { FeatureEnvironmentStore } from '../../db/feature-environment-store';
|
import { FeatureEnvironmentStore } from '../../db/feature-environment-store';
|
||||||
import ProjectStatsStore from '../../db/project-stats-store';
|
import ProjectStatsStore from '../../db/project-stats-store';
|
||||||
import {
|
import {
|
||||||
@ -29,7 +28,6 @@ import { FavoriteFeaturesStore } from '../../db/favorite-features-store';
|
|||||||
import { FavoriteProjectsStore } from '../../db/favorite-projects-store';
|
import { FavoriteProjectsStore } from '../../db/favorite-projects-store';
|
||||||
import FakeProjectStore from '../../../test/fixtures/fake-project-store';
|
import FakeProjectStore from '../../../test/fixtures/fake-project-store';
|
||||||
import FakeFeatureToggleStore from '../feature-toggle/fakes/fake-feature-toggle-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 FakeEnvironmentStore from '../../../test/fixtures/fake-environment-store';
|
||||||
import FakeFeatureEnvironmentStore from '../../../test/fixtures/fake-feature-environment-store';
|
import FakeFeatureEnvironmentStore from '../../../test/fixtures/fake-feature-environment-store';
|
||||||
import FakeProjectStatsStore from '../../../test/fixtures/fake-project-stats-store';
|
import FakeProjectStatsStore from '../../../test/fixtures/fake-project-stats-store';
|
||||||
@ -41,8 +39,6 @@ import {
|
|||||||
createPrivateProjectChecker,
|
createPrivateProjectChecker,
|
||||||
} from '../private-project/createPrivateProjectChecker';
|
} from '../private-project/createPrivateProjectChecker';
|
||||||
import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store';
|
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 = (
|
export const createProjectService = (
|
||||||
db: Db,
|
db: Db,
|
||||||
@ -63,7 +59,6 @@ export const createProjectService = (
|
|||||||
getLogger,
|
getLogger,
|
||||||
flagResolver,
|
flagResolver,
|
||||||
);
|
);
|
||||||
const featureTypeStore = new FeatureTypeStore(db, getLogger);
|
|
||||||
const accountStore = new AccountStore(db, getLogger);
|
const accountStore = new AccountStore(db, getLogger);
|
||||||
const environmentStore = new EnvironmentStore(db, eventBus, getLogger);
|
const environmentStore = new EnvironmentStore(db, eventBus, getLogger);
|
||||||
const featureEnvironmentStore = new FeatureEnvironmentStore(
|
const featureEnvironmentStore = new FeatureEnvironmentStore(
|
||||||
@ -106,14 +101,12 @@ export const createProjectService = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const privateProjectChecker = createPrivateProjectChecker(db, config);
|
const privateProjectChecker = createPrivateProjectChecker(db, config);
|
||||||
const lastSeenReadModel = new LastSeenAtReadModel(db);
|
|
||||||
|
|
||||||
return new ProjectService(
|
return new ProjectService(
|
||||||
{
|
{
|
||||||
projectStore,
|
projectStore,
|
||||||
eventStore,
|
eventStore,
|
||||||
featureToggleStore,
|
featureToggleStore,
|
||||||
featureTypeStore,
|
|
||||||
environmentStore,
|
environmentStore,
|
||||||
featureEnvironmentStore,
|
featureEnvironmentStore,
|
||||||
accountStore,
|
accountStore,
|
||||||
@ -126,7 +119,6 @@ export const createProjectService = (
|
|||||||
favoriteService,
|
favoriteService,
|
||||||
eventService,
|
eventService,
|
||||||
privateProjectChecker,
|
privateProjectChecker,
|
||||||
lastSeenReadModel,
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -138,7 +130,6 @@ export const createFakeProjectService = (
|
|||||||
const projectStore = new FakeProjectStore();
|
const projectStore = new FakeProjectStore();
|
||||||
const groupStore = new FakeGroupStore();
|
const groupStore = new FakeGroupStore();
|
||||||
const featureToggleStore = new FakeFeatureToggleStore();
|
const featureToggleStore = new FakeFeatureToggleStore();
|
||||||
const featureTypeStore = new FakeFeatureTypeStore();
|
|
||||||
const accountStore = new FakeAccountStore();
|
const accountStore = new FakeAccountStore();
|
||||||
const environmentStore = new FakeEnvironmentStore();
|
const environmentStore = new FakeEnvironmentStore();
|
||||||
const featureEnvironmentStore = new FakeFeatureEnvironmentStore();
|
const featureEnvironmentStore = new FakeFeatureEnvironmentStore();
|
||||||
@ -169,14 +160,12 @@ export const createFakeProjectService = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const privateProjectChecker = createFakePrivateProjectChecker();
|
const privateProjectChecker = createFakePrivateProjectChecker();
|
||||||
const fakeLastSeenReadModel = new FakeLastSeenReadModel();
|
|
||||||
|
|
||||||
return new ProjectService(
|
return new ProjectService(
|
||||||
{
|
{
|
||||||
projectStore,
|
projectStore,
|
||||||
eventStore,
|
eventStore,
|
||||||
featureToggleStore,
|
featureToggleStore,
|
||||||
featureTypeStore,
|
|
||||||
environmentStore,
|
environmentStore,
|
||||||
featureEnvironmentStore,
|
featureEnvironmentStore,
|
||||||
accountStore,
|
accountStore,
|
||||||
@ -189,6 +178,5 @@ export const createFakeProjectService = (
|
|||||||
favoriteService,
|
favoriteService,
|
||||||
eventService,
|
eventService,
|
||||||
privateProjectChecker,
|
privateProjectChecker,
|
||||||
fakeLastSeenReadModel,
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -568,7 +568,7 @@ export class AccessService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async removeDefaultProjectRoles(
|
async removeDefaultProjectRoles(
|
||||||
owner: User,
|
owner: IUser,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this.logger.info(`Removing project roles for ${projectId}`);
|
this.logger.info(`Removing project roles for ${projectId}`);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { subDays } from 'date-fns';
|
import { subDays } from 'date-fns';
|
||||||
import { ValidationError } from 'joi';
|
import { ValidationError } from 'joi';
|
||||||
import User, { IUser } from '../types/user';
|
import { IUser } from '../types/user';
|
||||||
import { AccessService, AccessWithRoles } from './access-service';
|
import { AccessService, AccessWithRoles } from './access-service';
|
||||||
import NameExistsError from '../error/name-exists-error';
|
import NameExistsError from '../error/name-exists-error';
|
||||||
import InvalidOperationError from '../error/invalid-operation-error';
|
import InvalidOperationError from '../error/invalid-operation-error';
|
||||||
@ -15,7 +15,6 @@ import {
|
|||||||
IEventStore,
|
IEventStore,
|
||||||
IFeatureEnvironmentStore,
|
IFeatureEnvironmentStore,
|
||||||
IFeatureToggleStore,
|
IFeatureToggleStore,
|
||||||
IFeatureTypeStore,
|
|
||||||
IProject,
|
IProject,
|
||||||
IProjectOverview,
|
IProjectOverview,
|
||||||
IProjectWithCount,
|
IProjectWithCount,
|
||||||
@ -65,8 +64,6 @@ import { ProjectDoraMetricsSchema } from 'lib/openapi';
|
|||||||
import { checkFeatureNamingData } from '../features/feature-naming-pattern/feature-naming-validation';
|
import { checkFeatureNamingData } from '../features/feature-naming-pattern/feature-naming-validation';
|
||||||
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
|
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
|
||||||
import EventService from './event-service';
|
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';
|
const getCreatedBy = (user: IUser) => user.email || user.username || 'unknown';
|
||||||
|
|
||||||
@ -89,6 +86,10 @@ interface ICalculateStatus {
|
|||||||
updates: IProjectStats;
|
updates: IProjectStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function includes(list: number[], { id }: { id: number }): boolean {
|
||||||
|
return list.some((l) => l === id);
|
||||||
|
}
|
||||||
|
|
||||||
export default class ProjectService {
|
export default class ProjectService {
|
||||||
private projectStore: IProjectStore;
|
private projectStore: IProjectStore;
|
||||||
|
|
||||||
@ -98,8 +99,6 @@ export default class ProjectService {
|
|||||||
|
|
||||||
private featureToggleStore: IFeatureToggleStore;
|
private featureToggleStore: IFeatureToggleStore;
|
||||||
|
|
||||||
private featureTypeStore: IFeatureTypeStore;
|
|
||||||
|
|
||||||
private featureEnvironmentStore: IFeatureEnvironmentStore;
|
private featureEnvironmentStore: IFeatureEnvironmentStore;
|
||||||
|
|
||||||
private environmentStore: IEnvironmentStore;
|
private environmentStore: IEnvironmentStore;
|
||||||
@ -120,8 +119,6 @@ export default class ProjectService {
|
|||||||
|
|
||||||
private projectStatsStore: IProjectStatsStore;
|
private projectStatsStore: IProjectStatsStore;
|
||||||
|
|
||||||
private lastSeenReadModel: ILastSeenReadModel;
|
|
||||||
|
|
||||||
private flagResolver: IFlagResolver;
|
private flagResolver: IFlagResolver;
|
||||||
|
|
||||||
private isEnterprise: boolean;
|
private isEnterprise: boolean;
|
||||||
@ -131,7 +128,6 @@ export default class ProjectService {
|
|||||||
projectStore,
|
projectStore,
|
||||||
eventStore,
|
eventStore,
|
||||||
featureToggleStore,
|
featureToggleStore,
|
||||||
featureTypeStore,
|
|
||||||
environmentStore,
|
environmentStore,
|
||||||
featureEnvironmentStore,
|
featureEnvironmentStore,
|
||||||
accountStore,
|
accountStore,
|
||||||
@ -141,7 +137,6 @@ export default class ProjectService {
|
|||||||
| 'projectStore'
|
| 'projectStore'
|
||||||
| 'eventStore'
|
| 'eventStore'
|
||||||
| 'featureToggleStore'
|
| 'featureToggleStore'
|
||||||
| 'featureTypeStore'
|
|
||||||
| 'environmentStore'
|
| 'environmentStore'
|
||||||
| 'featureEnvironmentStore'
|
| 'featureEnvironmentStore'
|
||||||
| 'accountStore'
|
| 'accountStore'
|
||||||
@ -154,7 +149,6 @@ export default class ProjectService {
|
|||||||
favoriteService: FavoritesService,
|
favoriteService: FavoritesService,
|
||||||
eventService: EventService,
|
eventService: EventService,
|
||||||
privateProjectChecker: IPrivateProjectChecker,
|
privateProjectChecker: IPrivateProjectChecker,
|
||||||
lastSeenReadModel: ILastSeenReadModel,
|
|
||||||
) {
|
) {
|
||||||
this.projectStore = projectStore;
|
this.projectStore = projectStore;
|
||||||
this.environmentStore = environmentStore;
|
this.environmentStore = environmentStore;
|
||||||
@ -162,7 +156,6 @@ export default class ProjectService {
|
|||||||
this.accessService = accessService;
|
this.accessService = accessService;
|
||||||
this.eventStore = eventStore;
|
this.eventStore = eventStore;
|
||||||
this.featureToggleStore = featureToggleStore;
|
this.featureToggleStore = featureToggleStore;
|
||||||
this.featureTypeStore = featureTypeStore;
|
|
||||||
this.featureToggleService = featureToggleService;
|
this.featureToggleService = featureToggleService;
|
||||||
this.favoritesService = favoriteService;
|
this.favoritesService = favoriteService;
|
||||||
this.privateProjectChecker = privateProjectChecker;
|
this.privateProjectChecker = privateProjectChecker;
|
||||||
@ -170,7 +163,6 @@ export default class ProjectService {
|
|||||||
this.groupService = groupService;
|
this.groupService = groupService;
|
||||||
this.eventService = eventService;
|
this.eventService = eventService;
|
||||||
this.projectStatsStore = projectStatsStore;
|
this.projectStatsStore = projectStatsStore;
|
||||||
this.lastSeenReadModel = lastSeenReadModel;
|
|
||||||
this.logger = config.getLogger('services/project-service.js');
|
this.logger = config.getLogger('services/project-service.js');
|
||||||
this.flagResolver = config.flagResolver;
|
this.flagResolver = config.flagResolver;
|
||||||
this.isEnterprise = config.isEnterprise;
|
this.isEnterprise = config.isEnterprise;
|
||||||
@ -267,7 +259,7 @@ export default class ProjectService {
|
|||||||
return data;
|
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);
|
const preData = await this.projectStore.get(updatedProject.id);
|
||||||
|
|
||||||
await this.projectStore.update(updatedProject);
|
await this.projectStore.update(updatedProject);
|
||||||
@ -283,7 +275,7 @@ export default class ProjectService {
|
|||||||
|
|
||||||
async updateProjectEnterpriseSettings(
|
async updateProjectEnterpriseSettings(
|
||||||
updatedProject: IProjectEnterpriseSettingsUpdate,
|
updatedProject: IProjectEnterpriseSettingsUpdate,
|
||||||
user: User,
|
user: IUser,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const preData = await this.projectStore.get(updatedProject.id);
|
const preData = await this.projectStore.get(updatedProject.id);
|
||||||
|
|
||||||
@ -330,7 +322,7 @@ export default class ProjectService {
|
|||||||
async changeProject(
|
async changeProject(
|
||||||
newProjectId: string,
|
newProjectId: string,
|
||||||
featureName: string,
|
featureName: string,
|
||||||
user: User,
|
user: IUser,
|
||||||
currentProjectId: string,
|
currentProjectId: string,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const feature = await this.featureToggleStore.get(featureName);
|
const feature = await this.featureToggleStore.get(featureName);
|
||||||
@ -372,7 +364,7 @@ export default class ProjectService {
|
|||||||
return updatedFeature;
|
return updatedFeature;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteProject(id: string, user: User): Promise<void> {
|
async deleteProject(id: string, user: IUser): Promise<void> {
|
||||||
if (id === DEFAULT_PROJECT) {
|
if (id === DEFAULT_PROJECT) {
|
||||||
throw new InvalidOperationError(
|
throw new InvalidOperationError(
|
||||||
'You can not delete the default project!',
|
'You can not delete the default project!',
|
||||||
@ -508,6 +500,11 @@ export default class ProjectService {
|
|||||||
userId,
|
userId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ownerRole = await this.accessService.getRoleByName(
|
||||||
|
RoleName.OWNER,
|
||||||
|
);
|
||||||
|
await this.validateAtLeastOneOwner(projectId, ownerRole);
|
||||||
|
|
||||||
await this.accessService.removeUserAccess(projectId, userId);
|
await this.accessService.removeUserAccess(projectId, userId);
|
||||||
|
|
||||||
await this.eventService.storeEvent(
|
await this.eventService.storeEvent(
|
||||||
@ -532,6 +529,11 @@ export default class ProjectService {
|
|||||||
groupId,
|
groupId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ownerRole = await this.accessService.getRoleByName(
|
||||||
|
RoleName.OWNER,
|
||||||
|
);
|
||||||
|
await this.validateAtLeastOneOwner(projectId, ownerRole);
|
||||||
|
|
||||||
await this.accessService.removeGroupAccess(projectId, groupId);
|
await this.accessService.removeGroupAccess(projectId, groupId);
|
||||||
|
|
||||||
await this.eventService.storeEvent(
|
await this.eventService.storeEvent(
|
||||||
@ -598,6 +600,8 @@ export default class ProjectService {
|
|||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this.validateAtLeastOneOwner(projectId, role);
|
||||||
|
|
||||||
await this.accessService.removeGroupFromRole(
|
await this.accessService.removeGroupFromRole(
|
||||||
group.id,
|
group.id,
|
||||||
role.id,
|
role.id,
|
||||||
@ -675,28 +679,39 @@ export default class ProjectService {
|
|||||||
async setRolesForUser(
|
async setRolesForUser(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
userId: number,
|
userId: number,
|
||||||
roles: number[],
|
newRoles: number[],
|
||||||
createdByUserName: string,
|
createdByUserName: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const existingRoles = await this.accessService.getProjectRolesForUser(
|
const currentRoles = await this.accessService.getProjectRolesForUser(
|
||||||
projectId,
|
projectId,
|
||||||
userId,
|
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(
|
await this.accessService.setProjectRolesForUser(
|
||||||
projectId,
|
projectId,
|
||||||
userId,
|
userId,
|
||||||
roles,
|
newRoles,
|
||||||
);
|
);
|
||||||
await this.eventService.storeEvent(
|
await this.eventService.storeEvent(
|
||||||
new ProjectAccessUserRolesUpdated({
|
new ProjectAccessUserRolesUpdated({
|
||||||
project: projectId,
|
project: projectId,
|
||||||
createdBy: createdByUserName,
|
createdBy: createdByUserName,
|
||||||
data: {
|
data: {
|
||||||
roles,
|
roles: newRoles,
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
preData: {
|
preData: {
|
||||||
roles: existingRoles,
|
roles: currentRoles,
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -706,17 +721,28 @@ export default class ProjectService {
|
|||||||
async setRolesForGroup(
|
async setRolesForGroup(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
groupId: number,
|
groupId: number,
|
||||||
roles: number[],
|
newRoles: number[],
|
||||||
createdBy: string,
|
createdBy: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const existingRoles = await this.accessService.getProjectRolesForGroup(
|
const currentRoles = await this.accessService.getProjectRolesForGroup(
|
||||||
projectId,
|
projectId,
|
||||||
groupId,
|
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(
|
await this.accessService.setProjectRolesForGroup(
|
||||||
projectId,
|
projectId,
|
||||||
groupId,
|
groupId,
|
||||||
roles,
|
newRoles,
|
||||||
createdBy,
|
createdBy,
|
||||||
);
|
);
|
||||||
await this.eventService.storeEvent(
|
await this.eventService.storeEvent(
|
||||||
@ -724,11 +750,11 @@ export default class ProjectService {
|
|||||||
project: projectId,
|
project: projectId,
|
||||||
createdBy,
|
createdBy,
|
||||||
data: {
|
data: {
|
||||||
roles,
|
roles: newRoles,
|
||||||
groupId,
|
groupId,
|
||||||
},
|
},
|
||||||
preData: {
|
preData: {
|
||||||
roles: existingRoles,
|
roles: currentRoles,
|
||||||
groupId,
|
groupId,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -1091,7 +1117,7 @@ export default class ProjectService {
|
|||||||
return {
|
return {
|
||||||
stats: projectStats,
|
stats: projectStats,
|
||||||
name: project.name,
|
name: project.name,
|
||||||
description: project.description,
|
description: project.description!,
|
||||||
mode: project.mode,
|
mode: project.mode,
|
||||||
featureLimit: project.featureLimit,
|
featureLimit: project.featureLimit,
|
||||||
featureNaming: project.featureNaming,
|
featureNaming: project.featureNaming,
|
||||||
|
@ -17,8 +17,10 @@ import {
|
|||||||
createFeatureToggleService,
|
createFeatureToggleService,
|
||||||
createProjectService,
|
createProjectService,
|
||||||
} from '../../../lib/features';
|
} from '../../../lib/features';
|
||||||
|
import { IGroup, IUnleashStores } from 'lib/types';
|
||||||
|
import { User } from 'lib/server-impl';
|
||||||
|
|
||||||
let stores;
|
let stores: IUnleashStores;
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
|
|
||||||
let projectService: ProjectService;
|
let projectService: ProjectService;
|
||||||
@ -26,7 +28,8 @@ let accessService: AccessService;
|
|||||||
let eventService: EventService;
|
let eventService: EventService;
|
||||||
let environmentService: EnvironmentService;
|
let environmentService: EnvironmentService;
|
||||||
let featureToggleService: FeatureToggleService;
|
let featureToggleService: FeatureToggleService;
|
||||||
let user;
|
let user: User; // many methods in this test use User instead of IUser
|
||||||
|
let group: IGroup;
|
||||||
|
|
||||||
const isProjectUser = async (
|
const isProjectUser = async (
|
||||||
userId: number,
|
userId: number,
|
||||||
@ -41,13 +44,17 @@ const isProjectUser = async (
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
db = await dbInit('project_service_serial', getLogger);
|
db = await dbInit('project_service_serial', getLogger);
|
||||||
stores = db.stores;
|
stores = db.stores;
|
||||||
|
// @ts-ignore return type IUser type missing generateImageUrl
|
||||||
user = await stores.userStore.insert({
|
user = await stores.userStore.insert({
|
||||||
name: 'Some Name',
|
name: 'Some Name',
|
||||||
email: 'test@getunleash.io',
|
email: 'test@getunleash.io',
|
||||||
});
|
});
|
||||||
|
group = await stores.groupStore.create({
|
||||||
|
name: 'aTestGroup',
|
||||||
|
description: '',
|
||||||
|
});
|
||||||
const config = createTestConfig({
|
const config = createTestConfig({
|
||||||
getLogger,
|
getLogger,
|
||||||
// @ts-ignore
|
|
||||||
experimental: {
|
experimental: {
|
||||||
flags: { privateProjects: true },
|
flags: { privateProjects: true },
|
||||||
},
|
},
|
||||||
@ -164,6 +171,7 @@ test('should not be able to delete project with toggles', async () => {
|
|||||||
await projectService.createProject(project, user);
|
await projectService.createProject(project, user);
|
||||||
await stores.featureToggleStore.create(project.id, {
|
await stores.featureToggleStore.create(project.id, {
|
||||||
name: 'test-project-delete',
|
name: 'test-project-delete',
|
||||||
|
// @ts-ignore project does not exist in type FeatureToggleDTO
|
||||||
project: project.id,
|
project: project.id,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
defaultStickiness: 'default',
|
defaultStickiness: 'default',
|
||||||
@ -491,31 +499,6 @@ test('should remove user from the project', async () => {
|
|||||||
expect(memberUsers).toHaveLength(0);
|
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 () => {
|
test('should not change project if feature toggle project does not match current project id', async () => {
|
||||||
const project = {
|
const project = {
|
||||||
id: 'test-change-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' };
|
const toggle = { name: 'test-toggle' };
|
||||||
|
|
||||||
await projectService.createProject(project, user);
|
await projectService.createProject(project, user);
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
await featureToggleService.createFeatureToggle(project.id, toggle, user);
|
await featureToggleService.createFeatureToggle(project.id, toggle, user);
|
||||||
|
|
||||||
try {
|
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' };
|
const toggle = { name: 'test-toggle-2' };
|
||||||
|
|
||||||
await projectService.createProject(project, user);
|
await projectService.createProject(project, user);
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
await featureToggleService.createFeatureToggle(project.id, toggle, user);
|
await featureToggleService.createFeatureToggle(project.id, toggle, user);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -594,6 +579,7 @@ test('should fail if user is not authorized', async () => {
|
|||||||
|
|
||||||
await projectService.createProject(project, user);
|
await projectService.createProject(project, user);
|
||||||
await projectService.createProject(projectDestination, projectAdmin1);
|
await projectService.createProject(projectDestination, projectAdmin1);
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
await featureToggleService.createFeatureToggle(project.id, toggle, user);
|
await featureToggleService.createFeatureToggle(project.id, toggle, user);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -626,6 +612,7 @@ test('should change project when checks pass', async () => {
|
|||||||
|
|
||||||
await projectService.createProject(projectA, user);
|
await projectService.createProject(projectA, user);
|
||||||
await projectService.createProject(projectB, user);
|
await projectService.createProject(projectB, user);
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
await featureToggleService.createFeatureToggle(projectA.id, toggle, user);
|
await featureToggleService.createFeatureToggle(projectA.id, toggle, user);
|
||||||
await projectService.changeProject(
|
await projectService.changeProject(
|
||||||
projectB.id,
|
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() };
|
const toggle = { name: randomId() };
|
||||||
await projectService.createProject(projectA, user);
|
await projectService.createProject(projectA, user);
|
||||||
await projectService.createProject(projectB, user);
|
await projectService.createProject(projectB, user);
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
await featureToggleService.createFeatureToggle(projectA.id, toggle, user);
|
await featureToggleService.createFeatureToggle(projectA.id, toggle, user);
|
||||||
const eventsBeforeChange = await stores.eventStore.getEvents();
|
const eventsBeforeChange = await stores.eventStore.getEvents();
|
||||||
await projectService.changeProject(
|
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(projectA, user);
|
||||||
await projectService.createProject(projectB, user);
|
await projectService.createProject(projectB, user);
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
await featureToggleService.createFeatureToggle(projectA.id, toggle, user);
|
await featureToggleService.createFeatureToggle(projectA.id, toggle, user);
|
||||||
await stores.environmentStore.create(environment);
|
await stores.environmentStore.create(environment);
|
||||||
await environmentService.addEnvironmentToProject(
|
await environmentService.addEnvironmentToProject(
|
||||||
@ -1013,6 +1002,38 @@ test('should able to assign role without existing members', async () => {
|
|||||||
expect(testUsers).toHaveLength(1);
|
expect(testUsers).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 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'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('should not update role for user on project when she is the owner', async () => {
|
test('should not update role for user on project when she is the owner', async () => {
|
||||||
const project = {
|
const project = {
|
||||||
id: 'update-users-not-allowed',
|
id: 'update-users-not-allowed',
|
||||||
@ -1028,7 +1049,9 @@ test('should not update role for user on project when she is the owner', async (
|
|||||||
email: 'update991@getunleash.io',
|
email: 'update991@getunleash.io',
|
||||||
});
|
});
|
||||||
|
|
||||||
const memberRole = await stores.roleStore.getRoleByName(RoleName.MEMBER);
|
const memberRole = await stores.roleStore.getRoleByName(
|
||||||
|
RoleName.MEMBER,
|
||||||
|
);
|
||||||
|
|
||||||
await projectService.addUser(
|
await projectService.addUser(
|
||||||
project.id,
|
project.id,
|
||||||
@ -1047,6 +1070,112 @@ test('should not update role for user on project when she is the owner', async (
|
|||||||
}).rejects.toThrowError(
|
}).rejects.toThrowError(
|
||||||
new Error('A project must have at least one owner'),
|
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',
|
||||||
|
);
|
||||||
|
|
||||||
|
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 () => {
|
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,
|
mode: 'open' as const,
|
||||||
defaultStickiness: 'clientId',
|
defaultStickiness: 'clientId',
|
||||||
};
|
};
|
||||||
|
// @ts-ignore user.id is wrong type should be user
|
||||||
await projectService.createProject(project, user.id);
|
await projectService.createProject(project, user.id);
|
||||||
const groupStore = stores.groupStore;
|
const groupStore = stores.groupStore;
|
||||||
|
|
||||||
@ -1124,6 +1254,7 @@ test('Should allow bulk update of only groups', async () => {
|
|||||||
};
|
};
|
||||||
const groupStore = stores.groupStore;
|
const groupStore = stores.groupStore;
|
||||||
|
|
||||||
|
// @ts-ignore user.id is wrong type should be user
|
||||||
await projectService.createProject(project, user.id);
|
await projectService.createProject(project, user.id);
|
||||||
|
|
||||||
const group1 = await groupStore.create({
|
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',
|
defaultStickiness: 'clientId',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @ts-ignore user.id is wrong type should be user
|
||||||
await projectService.createProject(project, user.id);
|
await projectService.createProject(project, user.id);
|
||||||
|
|
||||||
const group1 = await stores.groupStore.create({
|
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, {
|
await stores.featureToggleStore.create(project.id, {
|
||||||
name: 'only-active-t1',
|
name: 'only-active-t1',
|
||||||
|
// @ts-ignore project property does not exist in FeatureToggleDTO
|
||||||
project: project.id,
|
project: project.id,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
});
|
});
|
||||||
await stores.featureToggleStore.create(project.id, {
|
await stores.featureToggleStore.create(project.id, {
|
||||||
name: 'only-active-t2',
|
name: 'only-active-t2',
|
||||||
|
// @ts-ignore project property does not exist in FeatureToggleDTO
|
||||||
project: project.id,
|
project: project.id,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
});
|
});
|
||||||
@ -1261,6 +1395,7 @@ test('should list projects with all features archived', async () => {
|
|||||||
|
|
||||||
await stores.featureToggleStore.create(project.id, {
|
await stores.featureToggleStore.create(project.id, {
|
||||||
name: 'archived-toggle',
|
name: 'archived-toggle',
|
||||||
|
// @ts-ignore project property does not exist in FeatureToggleDTO
|
||||||
project: project.id,
|
project: project.id,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
});
|
});
|
||||||
@ -1294,6 +1429,7 @@ test('should calculate average time to production', async () => {
|
|||||||
defaultStickiness: 'clientId',
|
defaultStickiness: 'clientId',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @ts-ignore user.id is wrong type should be user
|
||||||
await projectService.createProject(project, user.id);
|
await projectService.createProject(project, user.id);
|
||||||
|
|
||||||
const toggles = [
|
const toggles = [
|
||||||
@ -1309,6 +1445,7 @@ test('should calculate average time to production', async () => {
|
|||||||
return featureToggleService.createFeatureToggle(
|
return featureToggleService.createFeatureToggle(
|
||||||
project.id,
|
project.id,
|
||||||
toggle,
|
toggle,
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -1360,6 +1497,7 @@ test('should calculate average time to production ignoring some items', async ()
|
|||||||
tags: [],
|
tags: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// @ts-ignore user.id is wrong type should be user
|
||||||
await projectService.createProject(project, user.id);
|
await projectService.createProject(project, user.id);
|
||||||
await stores.environmentStore.create({
|
await stores.environmentStore.create({
|
||||||
name: 'customEnv',
|
name: 'customEnv',
|
||||||
@ -1369,6 +1507,7 @@ test('should calculate average time to production ignoring some items', async ()
|
|||||||
|
|
||||||
// actual toggle we take for calculations
|
// actual toggle we take for calculations
|
||||||
const toggle = { name: 'main-toggle' };
|
const toggle = { name: 'main-toggle' };
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
await featureToggleService.createFeatureToggle(project.id, toggle, user);
|
await featureToggleService.createFeatureToggle(project.id, toggle, user);
|
||||||
await updateFeature(toggle.name, {
|
await updateFeature(toggle.name, {
|
||||||
created_at: subDays(new Date(), 20),
|
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
|
// ignore toggles enabled in non-prod envs
|
||||||
const devToggle = { name: 'dev-toggle' };
|
const devToggle = { name: 'dev-toggle' };
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
await featureToggleService.createFeatureToggle(project.id, devToggle, user);
|
await featureToggleService.createFeatureToggle(project.id, devToggle, user);
|
||||||
await eventService.storeEvent(
|
await eventService.storeEvent(
|
||||||
new FeatureEnvironmentEvent({
|
new FeatureEnvironmentEvent({
|
||||||
@ -1397,6 +1537,7 @@ test('should calculate average time to production ignoring some items', async ()
|
|||||||
await featureToggleService.createFeatureToggle(
|
await featureToggleService.createFeatureToggle(
|
||||||
'default',
|
'default',
|
||||||
otherProjectToggle,
|
otherProjectToggle,
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
await eventService.storeEvent(
|
await eventService.storeEvent(
|
||||||
@ -1408,6 +1549,7 @@ test('should calculate average time to production ignoring some items', async ()
|
|||||||
await featureToggleService.createFeatureToggle(
|
await featureToggleService.createFeatureToggle(
|
||||||
project.id,
|
project.id,
|
||||||
nonReleaseToggle,
|
nonReleaseToggle,
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
await eventService.storeEvent(
|
await eventService.storeEvent(
|
||||||
@ -1419,6 +1561,7 @@ test('should calculate average time to production ignoring some items', async ()
|
|||||||
await featureToggleService.createFeatureToggle(
|
await featureToggleService.createFeatureToggle(
|
||||||
project.id,
|
project.id,
|
||||||
previouslyDeleteToggle,
|
previouslyDeleteToggle,
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
await eventService.storeEvent(
|
await eventService.storeEvent(
|
||||||
@ -1441,6 +1584,7 @@ test('should get correct amount of features created in current and past window',
|
|||||||
defaultStickiness: 'clientId',
|
defaultStickiness: 'clientId',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @ts-ignore user.id is wrong type should be user
|
||||||
await projectService.createProject(project, user.id);
|
await projectService.createProject(project, user.id);
|
||||||
|
|
||||||
const toggles = [
|
const toggles = [
|
||||||
@ -1455,6 +1599,7 @@ test('should get correct amount of features created in current and past window',
|
|||||||
return featureToggleService.createFeatureToggle(
|
return featureToggleService.createFeatureToggle(
|
||||||
project.id,
|
project.id,
|
||||||
toggle,
|
toggle,
|
||||||
|
// @ts-ignore user is wrong parameter type
|
||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -1478,6 +1623,7 @@ test('should get correct amount of features archived in current and past window'
|
|||||||
defaultStickiness: 'clientId',
|
defaultStickiness: 'clientId',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @ts-ignore user.id is wrong parameter type, should be user
|
||||||
await projectService.createProject(project, user.id);
|
await projectService.createProject(project, user.id);
|
||||||
|
|
||||||
const toggles = [
|
const toggles = [
|
||||||
@ -1492,6 +1638,7 @@ test('should get correct amount of features archived in current and past window'
|
|||||||
return featureToggleService.createFeatureToggle(
|
return featureToggleService.createFeatureToggle(
|
||||||
project.id,
|
project.id,
|
||||||
toggle,
|
toggle,
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -1529,6 +1676,7 @@ test('should get correct amount of project members for current and past window',
|
|||||||
defaultStickiness: 'default',
|
defaultStickiness: 'default',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @ts-ignore user.id is wrong type should be user
|
||||||
await projectService.createProject(project, user.id);
|
await projectService.createProject(project, user.id);
|
||||||
|
|
||||||
const users = [
|
const users = [
|
||||||
@ -1569,6 +1717,7 @@ test('should return average time to production per toggle', async () => {
|
|||||||
defaultStickiness: 'clientId',
|
defaultStickiness: 'clientId',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @ts-ignore user.id is wrong type should be user
|
||||||
await projectService.createProject(project, user.id);
|
await projectService.createProject(project, user.id);
|
||||||
|
|
||||||
const toggles = [
|
const toggles = [
|
||||||
@ -1584,7 +1733,7 @@ test('should return average time to production per toggle', async () => {
|
|||||||
return featureToggleService.createFeatureToggle(
|
return featureToggleService.createFeatureToggle(
|
||||||
project.id,
|
project.id,
|
||||||
toggle,
|
toggle,
|
||||||
user,
|
user.email!,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -1633,7 +1782,9 @@ test('should return average time to production per toggle for a specific project
|
|||||||
defaultStickiness: 'clientId',
|
defaultStickiness: 'clientId',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @ts-ignore user.id is wrong parameter type, should be user
|
||||||
await projectService.createProject(project1, user.id);
|
await projectService.createProject(project1, user.id);
|
||||||
|
// @ts-ignore user.id is wrong parameter type, should be user
|
||||||
await projectService.createProject(project2, user.id);
|
await projectService.createProject(project2, user.id);
|
||||||
|
|
||||||
const togglesProject1 = [
|
const togglesProject1 = [
|
||||||
@ -1652,6 +1803,7 @@ test('should return average time to production per toggle for a specific project
|
|||||||
return featureToggleService.createFeatureToggle(
|
return featureToggleService.createFeatureToggle(
|
||||||
project1.id,
|
project1.id,
|
||||||
toggle,
|
toggle,
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -1662,6 +1814,7 @@ test('should return average time to production per toggle for a specific project
|
|||||||
return featureToggleService.createFeatureToggle(
|
return featureToggleService.createFeatureToggle(
|
||||||
project2.id,
|
project2.id,
|
||||||
toggle,
|
toggle,
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -1726,6 +1879,7 @@ test('should return average time to production per toggle and include archived t
|
|||||||
defaultStickiness: 'clientId',
|
defaultStickiness: 'clientId',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @ts-ignore user.id is wrong parameter type, should be user
|
||||||
await projectService.createProject(project1, user.id);
|
await projectService.createProject(project1, user.id);
|
||||||
|
|
||||||
const togglesProject1 = [
|
const togglesProject1 = [
|
||||||
@ -1739,6 +1893,7 @@ test('should return average time to production per toggle and include archived t
|
|||||||
return featureToggleService.createFeatureToggle(
|
return featureToggleService.createFeatureToggle(
|
||||||
project1.id,
|
project1.id,
|
||||||
toggle,
|
toggle,
|
||||||
|
// @ts-ignore user is wrong parameter type, should be string
|
||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -1790,6 +1945,7 @@ describe('feature flag naming patterns', () => {
|
|||||||
featureNaming,
|
featureNaming,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @ts-ignore user.id is wrong parameter type, should be user
|
||||||
await projectService.createProject(project, user.id);
|
await projectService.createProject(project, user.id);
|
||||||
|
|
||||||
await projectService.updateProjectEnterpriseSettings(project, user);
|
await projectService.updateProjectEnterpriseSettings(project, user);
|
||||||
@ -1804,6 +1960,7 @@ describe('feature flag naming patterns', () => {
|
|||||||
...project,
|
...project,
|
||||||
featureNaming: { pattern: newPattern },
|
featureNaming: { pattern: newPattern },
|
||||||
},
|
},
|
||||||
|
// @ts-ignore user.id is wrong parameter type, should be user
|
||||||
user.id,
|
user.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1822,10 +1979,12 @@ test('deleting a project with archived toggles should result in any remaining ar
|
|||||||
};
|
};
|
||||||
const toggleName = 'archived-and-deleted';
|
const toggleName = 'archived-and-deleted';
|
||||||
|
|
||||||
|
// @ts-ignore user.id is wrong parameter type, should be user
|
||||||
await projectService.createProject(project, user.id);
|
await projectService.createProject(project, user.id);
|
||||||
|
|
||||||
await stores.featureToggleStore.create(project.id, {
|
await stores.featureToggleStore.create(project.id, {
|
||||||
name: toggleName,
|
name: toggleName,
|
||||||
|
// @ts-ignore project property does not exist in FeatureToggleDTO
|
||||||
project: project.id,
|
project: project.id,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
defaultStickiness: 'default',
|
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
|
// bring the project back again, previously this would allow those archived toggles to be resurrected
|
||||||
// we now expect them to be deleted correctly
|
// 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);
|
await projectService.createProject(project, user.id);
|
||||||
|
|
||||||
const toggles = await stores.featureToggleStore.getAll({
|
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',
|
name: 'project-with-nothing',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @ts-ignore user.id is wrong parameter type, should be user
|
||||||
await projectService.createProject(project, user.id);
|
await projectService.createProject(project, user.id);
|
||||||
await projectService.deleteProject(project.id, user);
|
await projectService.deleteProject(project.id, user);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user