diff --git a/src/lib/features/project/project-service.test.ts b/src/lib/features/project/project-service.test.ts index 4ca7f09b58..34d9dc22e1 100644 --- a/src/lib/features/project/project-service.test.ts +++ b/src/lib/features/project/project-service.test.ts @@ -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', + }); + }); }); diff --git a/src/lib/features/project/project-service.ts b/src/lib/features/project/project-service.ts index a3c664b780..106866e4c7 100644 --- a/src/lib/features/project/project-service.ts +++ b/src/lib/features/project/project-service.ts @@ -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 { 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( diff --git a/src/lib/services/group-service.ts b/src/lib/services/group-service.ts index f83b74cd7b..d629211ba8 100644 --- a/src/lib/services/group-service.ts +++ b/src/lib/services/group-service.ts @@ -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 { + ): Promise { const projectGroups = await this.groupStore.getProjectGroups(projectId); if (projectGroups.length > 0) { diff --git a/src/lib/types/group.ts b/src/lib/types/group.ts index 5dc2b7002c..a4b387b425 100644 --- a/src/lib/types/group.ts +++ b/src/lib/types/group.ts @@ -53,8 +53,7 @@ export interface ICreateGroupUserModel { user: Pick; } -export interface IGroupModelWithProjectRole extends IGroupModel { - roleId: number; +export interface IGroupModelWithAddedAt extends IGroupModel { addedAt: Date; } diff --git a/src/lib/types/stores/access-store.ts b/src/lib/types/stores/access-store.ts index f7bdd288ca..9d571d106a 100644 --- a/src/lib/types/stores/access-store.ts +++ b/src/lib/types/stores/access-store.ts @@ -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 {