Skip to content

Commit a4b4344

Browse files
committed
chore: remove project at least one owner constraint
1 parent eca36ee commit a4b4344

File tree

7 files changed

+2
-366
lines changed

7 files changed

+2
-366
lines changed

frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx

+2-10
Original file line numberDiff line numberDiff line change
@@ -232,12 +232,8 @@ export const ProjectAccessTable: VFC = () => {
232232
? 'group'
233233
: 'user'
234234
}/${row.entity.id}`}
235-
disabled={access?.rows.length === 1}
236235
tooltipProps={{
237-
title:
238-
access?.rows.length === 1
239-
? 'Cannot edit access. A project must have at least one owner'
240-
: 'Edit access',
236+
title: 'Edit access',
241237
}}
242238
>
243239
<Edit />
@@ -253,12 +249,8 @@ export const ProjectAccessTable: VFC = () => {
253249
setSelectedRow(row);
254250
setRemoveOpen(true);
255251
}}
256-
disabled={access?.rows.length === 1}
257252
tooltipProps={{
258-
title:
259-
access?.rows.length === 1
260-
? 'Cannot remove access. A project must have at least one owner'
261-
: 'Remove access',
253+
title: 'Remove access',
262254
}}
263255
>
264256
<Delete />

src/lib/error/index.ts

-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import PermissionError from './permission-error';
1010
import { OperationDeniedError } from './operation-denied-error';
1111
import UserTokenError from './used-token-error';
1212
import RoleInUseError from './role-in-use-error';
13-
import ProjectWithoutOwnerError from './project-without-owner-error';
1413
import PasswordUndefinedError from './password-undefined';
1514
import PasswordMismatchError from './password-mismatch';
1615
import PatternError from './pattern-error';
@@ -32,7 +31,6 @@ export {
3231
OperationDeniedError,
3332
UserTokenError,
3433
RoleInUseError,
35-
ProjectWithoutOwnerError,
3634
PasswordUndefinedError,
3735
PatternError,
3836
PasswordMismatchError,

src/lib/error/project-without-owner-error.ts

-21
This file was deleted.

src/lib/error/unleash-error.test.ts

-15
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import PermissionError from './permission-error';
1010
import OwaspValidationError from './owasp-validation-error';
1111
import IncompatibleProjectError from './incompatible-project-error';
1212
import PasswordUndefinedError from './password-undefined';
13-
import ProjectWithoutOwnerError from './project-without-owner-error';
1413
import NotFoundError from './notfound-error';
1514
import { validateString } from '../util/validators/constraint-types';
1615
import { fromLegacyError } from './from-legacy-error';
@@ -671,20 +670,6 @@ describe('Error serialization special cases', () => {
671670
],
672671
});
673672
});
674-
675-
it('ProjectWithoutOwnerError: adds `validationErrors: []` to the `details` list', () => {
676-
const error = new ProjectWithoutOwnerError();
677-
const json = error.toJSON();
678-
679-
expect(json).toMatchObject({
680-
details: [
681-
{
682-
validationErrors: [],
683-
message: json.message,
684-
},
685-
],
686-
});
687-
});
688673
});
689674

690675
describe('Stack traces', () => {

src/lib/features/project/project-service.e2e.test.ts

-218
Original file line numberDiff line numberDiff line change
@@ -1587,224 +1587,6 @@ test('should able to assign role without existing members', async () => {
15871587
expect(testUsers).toHaveLength(1);
15881588
});
15891589

1590-
describe('ensure project has at least one owner', () => {
1591-
test('should not remove user from the project', async () => {
1592-
const project = {
1593-
id: 'remove-users-not-allowed',
1594-
name: 'New project',
1595-
description: 'Blah',
1596-
mode: 'open' as const,
1597-
defaultStickiness: 'clientId',
1598-
};
1599-
await projectService.createProject(project, user, auditUser);
1600-
1601-
const roles = await stores.roleStore.getRolesForProject(project.id);
1602-
const ownerRole = roles.find((r) => r.name === RoleName.OWNER)!;
1603-
1604-
await expect(async () => {
1605-
await projectService.removeUser(
1606-
project.id,
1607-
ownerRole.id,
1608-
user.id,
1609-
auditUser,
1610-
);
1611-
}).rejects.toThrowError(
1612-
new Error('A project must have at least one owner'),
1613-
);
1614-
1615-
await expect(async () => {
1616-
await projectService.removeUserAccess(
1617-
project.id,
1618-
user.id,
1619-
auditUser,
1620-
);
1621-
}).rejects.toThrowError(
1622-
new Error('A project must have at least one owner'),
1623-
);
1624-
});
1625-
1626-
test('should be able to remove member user from the project when another is owner', async () => {
1627-
const project = {
1628-
id: 'remove-users-members-allowed',
1629-
name: 'New project',
1630-
description: 'Blah',
1631-
mode: 'open' as const,
1632-
defaultStickiness: 'clientId',
1633-
};
1634-
await projectService.createProject(project, user, auditUser);
1635-
1636-
const memberRole = await stores.roleStore.getRoleByName(
1637-
RoleName.MEMBER,
1638-
);
1639-
1640-
const memberUser = await stores.userStore.insert({
1641-
name: 'Some Name',
1642-
1643-
});
1644-
1645-
await projectService.addAccess(
1646-
project.id,
1647-
[memberRole.id],
1648-
[],
1649-
[memberUser.id],
1650-
auditUser,
1651-
);
1652-
1653-
const usersBefore = await projectService.getProjectUsers(project.id);
1654-
await projectService.removeUserAccess(
1655-
project.id,
1656-
memberUser.id,
1657-
auditUser,
1658-
);
1659-
const usersAfter = await projectService.getProjectUsers(project.id);
1660-
expect(usersBefore).toHaveLength(2);
1661-
expect(usersAfter).toHaveLength(1);
1662-
});
1663-
1664-
test('should not update role for user on project when she is the owner', async () => {
1665-
const project = {
1666-
id: 'update-users-not-allowed',
1667-
name: 'New project',
1668-
description: 'Blah',
1669-
mode: 'open' as const,
1670-
defaultStickiness: 'clientId',
1671-
};
1672-
await projectService.createProject(project, user, auditUser);
1673-
1674-
const projectMember1 = await stores.userStore.insert({
1675-
name: 'Some Member',
1676-
1677-
});
1678-
1679-
const memberRole = await stores.roleStore.getRoleByName(
1680-
RoleName.MEMBER,
1681-
);
1682-
1683-
await projectService.addUser(
1684-
project.id,
1685-
memberRole.id,
1686-
projectMember1.id,
1687-
auditUser,
1688-
);
1689-
1690-
await expect(async () => {
1691-
await projectService.changeRole(
1692-
project.id,
1693-
memberRole.id,
1694-
user.id,
1695-
auditUser,
1696-
);
1697-
}).rejects.toThrowError(
1698-
new Error('A project must have at least one owner'),
1699-
);
1700-
1701-
await expect(async () => {
1702-
await projectService.setRolesForUser(
1703-
project.id,
1704-
user.id,
1705-
[memberRole.id],
1706-
auditUser,
1707-
);
1708-
}).rejects.toThrowError(
1709-
new Error('A project must have at least one owner'),
1710-
);
1711-
});
1712-
1713-
async function projectWithGroupOwner(projectId: string) {
1714-
const project = {
1715-
id: projectId,
1716-
name: 'New project',
1717-
description: 'Blah',
1718-
mode: 'open' as const,
1719-
defaultStickiness: 'clientId',
1720-
};
1721-
await projectService.createProject(project, user, auditUser);
1722-
1723-
const roles = await stores.roleStore.getRolesForProject(project.id);
1724-
const ownerRole = roles.find((r) => r.name === RoleName.OWNER)!;
1725-
1726-
await projectService.addGroup(
1727-
project.id,
1728-
ownerRole.id,
1729-
group.id,
1730-
auditUser,
1731-
);
1732-
1733-
// this should be fine, leaving the group as the only owner
1734-
// note group has zero members, but it still acts as an owner
1735-
await projectService.removeUser(
1736-
project.id,
1737-
ownerRole.id,
1738-
user.id,
1739-
auditUser,
1740-
);
1741-
1742-
return {
1743-
project,
1744-
group,
1745-
ownerRole,
1746-
};
1747-
}
1748-
1749-
test('should not remove group from the project', async () => {
1750-
const { project, group, ownerRole } = await projectWithGroupOwner(
1751-
'remove-group-not-allowed',
1752-
);
1753-
1754-
await expect(async () => {
1755-
await projectService.removeGroup(
1756-
project.id,
1757-
ownerRole.id,
1758-
group.id,
1759-
auditUser,
1760-
);
1761-
}).rejects.toThrowError(
1762-
new Error('A project must have at least one owner'),
1763-
);
1764-
1765-
await expect(async () => {
1766-
await projectService.removeGroupAccess(
1767-
project.id,
1768-
group.id,
1769-
auditUser,
1770-
);
1771-
}).rejects.toThrowError(
1772-
new Error('A project must have at least one owner'),
1773-
);
1774-
});
1775-
1776-
test('should not update role for group on project when she is the owner', async () => {
1777-
const { project, group } = await projectWithGroupOwner(
1778-
'update-group-not-allowed',
1779-
);
1780-
const memberRole = await stores.roleStore.getRoleByName(
1781-
RoleName.MEMBER,
1782-
);
1783-
1784-
await expect(async () => {
1785-
await projectService.changeGroupRole(
1786-
project.id,
1787-
memberRole.id,
1788-
group.id,
1789-
auditUser,
1790-
);
1791-
}).rejects.toThrowError(
1792-
new Error('A project must have at least one owner'),
1793-
);
1794-
1795-
await expect(async () => {
1796-
await projectService.setRolesForGroup(
1797-
project.id,
1798-
group.id,
1799-
[memberRole.id],
1800-
auditUser,
1801-
);
1802-
}).rejects.toThrowError(
1803-
new Error('A project must have at least one owner'),
1804-
);
1805-
});
1806-
});
1807-
18081590
test('Should allow bulk update of group permissions', async () => {
18091591
const project = {
18101592
id: 'bulk-update-project',

src/lib/features/project/project-service.test.ts

-41
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { createTestConfig } from '../../../test/config/test-config';
22
import { BadDataError } from '../../error';
33
import { type IBaseEvent, RoleName, TEST_AUDIT_USER } from '../../types';
44
import { createFakeProjectService } from './createProjectService';
5-
import ProjectService from './project-service';
65

76
describe('enterprise extension: enable change requests', () => {
87
const createService = () => {
@@ -301,44 +300,4 @@ describe('enterprise extension: enable change requests', () => {
301300
),
302301
).resolves.toBeTruthy();
303302
});
304-
305-
test('has at least one owner after deletion when group has the role and a project user for role exists', async () => {
306-
const config = createTestConfig();
307-
const projectId = 'fake-project-id';
308-
const service = new ProjectService(
309-
{
310-
projectStore: {} as any,
311-
projectOwnersReadModel: {} as any,
312-
projectFlagCreatorsReadModel: {} as any,
313-
eventStore: {} as any,
314-
featureToggleStore: {} as any,
315-
environmentStore: {} as any,
316-
featureEnvironmentStore: {} as any,
317-
accountStore: {} as any,
318-
projectStatsStore: {} as any,
319-
projectReadModel: {} as any,
320-
onboardingReadModel: {} as any,
321-
},
322-
config,
323-
{
324-
getProjectUsersForRole: async () =>
325-
Promise.resolve([{ id: 1 } as any]),
326-
} as any,
327-
{} as any,
328-
{
329-
getProjectGroups: async () =>
330-
Promise.resolve([{ roles: [2, 5] } as any]),
331-
} as any,
332-
{} as any,
333-
{} as any,
334-
{} as any,
335-
{} as any,
336-
);
337-
338-
await service.validateAtLeastOneOwner(projectId, {
339-
id: 5,
340-
name: 'Owner',
341-
type: 'Owner',
342-
});
343-
});
344303
});

0 commit comments

Comments
 (0)