mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	fix: group and user has at least 1 project owner counter respects multirole project groups (#8453)
This commit is contained in:
		
							parent
							
								
									b9ea6641ff
								
							
						
					
					
						commit
						bb800e3537
					
				@ -2,6 +2,7 @@ import { createTestConfig } from '../../../test/config/test-config';
 | 
			
		||||
import { BadDataError } from '../../error';
 | 
			
		||||
import { type IBaseEvent, RoleName, TEST_AUDIT_USER } from '../../types';
 | 
			
		||||
import { createFakeProjectService } from './createProjectService';
 | 
			
		||||
import ProjectService from './project-service';
 | 
			
		||||
 | 
			
		||||
describe('enterprise extension: enable change requests', () => {
 | 
			
		||||
    const createService = () => {
 | 
			
		||||
@ -300,4 +301,44 @@ describe('enterprise extension: enable change requests', () => {
 | 
			
		||||
            ),
 | 
			
		||||
        ).resolves.toBeTruthy();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('has at least one owner after deletion when group has the role and a project user for role exists', async () => {
 | 
			
		||||
        const config = createTestConfig();
 | 
			
		||||
        const projectId = 'fake-project-id';
 | 
			
		||||
        const service = new ProjectService(
 | 
			
		||||
            {
 | 
			
		||||
                projectStore: {} as any,
 | 
			
		||||
                projectOwnersReadModel: {} as any,
 | 
			
		||||
                projectFlagCreatorsReadModel: {} as any,
 | 
			
		||||
                eventStore: {} as any,
 | 
			
		||||
                featureToggleStore: {} as any,
 | 
			
		||||
                environmentStore: {} as any,
 | 
			
		||||
                featureEnvironmentStore: {} as any,
 | 
			
		||||
                accountStore: {} as any,
 | 
			
		||||
                projectStatsStore: {} as any,
 | 
			
		||||
                projectReadModel: {} as any,
 | 
			
		||||
                onboardingReadModel: {} as any,
 | 
			
		||||
            },
 | 
			
		||||
            config,
 | 
			
		||||
            {
 | 
			
		||||
                getProjectUsersForRole: async () =>
 | 
			
		||||
                    Promise.resolve([{ id: 1 } as any]),
 | 
			
		||||
            } as any,
 | 
			
		||||
            {} as any,
 | 
			
		||||
            {
 | 
			
		||||
                getProjectGroups: async () =>
 | 
			
		||||
                    Promise.resolve([{ roles: [2, 5] } as any]),
 | 
			
		||||
            } as any,
 | 
			
		||||
            {} as any,
 | 
			
		||||
            {} as any,
 | 
			
		||||
            {} as any,
 | 
			
		||||
            {} as any,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        await service.validateAtLeastOneOwner(projectId, {
 | 
			
		||||
            id: 5,
 | 
			
		||||
            name: 'Owner',
 | 
			
		||||
            type: 'Owner',
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -1146,8 +1146,8 @@ export default class ProjectService {
 | 
			
		||||
                projectId,
 | 
			
		||||
            );
 | 
			
		||||
            const groups = await this.groupService.getProjectGroups(projectId);
 | 
			
		||||
            const roleGroups = groups.filter(
 | 
			
		||||
                (g) => g.roleId === currentRole.id,
 | 
			
		||||
            const roleGroups = groups.filter((g) =>
 | 
			
		||||
                g.roles?.includes(currentRole.id),
 | 
			
		||||
            );
 | 
			
		||||
            if (users.length + roleGroups.length < 2) {
 | 
			
		||||
                throw new ProjectWithoutOwnerError();
 | 
			
		||||
@ -1264,11 +1264,11 @@ export default class ProjectService {
 | 
			
		||||
        auditUser: IAuditUser,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const usersWithRoles = await this.getAccessToProject(projectId);
 | 
			
		||||
        const user = usersWithRoles.groups.find((u) => u.id === userId);
 | 
			
		||||
        if (!user)
 | 
			
		||||
        const userGroup = usersWithRoles.groups.find((u) => u.id === userId);
 | 
			
		||||
        if (!userGroup)
 | 
			
		||||
            throw new ValidationError('Unexpected empty user', [], undefined);
 | 
			
		||||
        const currentRole = usersWithRoles.roles.find(
 | 
			
		||||
            (r) => r.id === user.roleId,
 | 
			
		||||
        const currentRole = usersWithRoles.roles.find((r) =>
 | 
			
		||||
            userGroup.roles?.includes(r.id),
 | 
			
		||||
        );
 | 
			
		||||
        if (!currentRole)
 | 
			
		||||
            throw new ValidationError(
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,6 @@ import type {
 | 
			
		||||
    ICreateGroupModel,
 | 
			
		||||
    IGroup,
 | 
			
		||||
    IGroupModel,
 | 
			
		||||
    IGroupModelWithProjectRole,
 | 
			
		||||
    IGroupProject,
 | 
			
		||||
    IGroupRole,
 | 
			
		||||
    IGroupUser,
 | 
			
		||||
@ -29,6 +28,7 @@ import type { IAccountStore } from '../types/stores/account-store';
 | 
			
		||||
import type { IUser } from '../types/user';
 | 
			
		||||
import type EventService from '../features/events/event-service';
 | 
			
		||||
import { SSO_SYNC_USER } from '../db/group-store';
 | 
			
		||||
import type { IGroupWithProjectRoles } from '../types/stores/access-store';
 | 
			
		||||
 | 
			
		||||
const setsAreEqual = (firstSet, secondSet) =>
 | 
			
		||||
    firstSet.size === secondSet.size &&
 | 
			
		||||
@ -179,7 +179,7 @@ export class GroupService {
 | 
			
		||||
 | 
			
		||||
    async getProjectGroups(
 | 
			
		||||
        projectId: string,
 | 
			
		||||
    ): Promise<IGroupModelWithProjectRole[]> {
 | 
			
		||||
    ): Promise<IGroupWithProjectRoles[]> {
 | 
			
		||||
        const projectGroups = await this.groupStore.getProjectGroups(projectId);
 | 
			
		||||
 | 
			
		||||
        if (projectGroups.length > 0) {
 | 
			
		||||
 | 
			
		||||
@ -53,8 +53,7 @@ export interface ICreateGroupUserModel {
 | 
			
		||||
    user: Pick<IUser, 'id'>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IGroupModelWithProjectRole extends IGroupModel {
 | 
			
		||||
    roleId: number;
 | 
			
		||||
export interface IGroupModelWithAddedAt extends IGroupModel {
 | 
			
		||||
    addedAt: Date;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import type { PermissionRef } from '../../services/access-service';
 | 
			
		||||
import type { IGroupModelWithProjectRole } from '../group';
 | 
			
		||||
import type { IGroupModelWithAddedAt } from '../group';
 | 
			
		||||
import type { IPermission, IUserAccessOverview, IUserWithRole } from '../model';
 | 
			
		||||
import type { Store } from './store';
 | 
			
		||||
 | 
			
		||||
@ -62,7 +62,7 @@ export interface IUserWithProjectRoles
 | 
			
		||||
    extends IUserWithRole,
 | 
			
		||||
        IEntityWithProjectRoles {}
 | 
			
		||||
export interface IGroupWithProjectRoles
 | 
			
		||||
    extends IGroupModelWithProjectRole,
 | 
			
		||||
    extends IGroupModelWithAddedAt,
 | 
			
		||||
        IEntityWithProjectRoles {}
 | 
			
		||||
 | 
			
		||||
export interface IAccessStore extends Store<IRole, number> {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user