Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const DecisionInstanceHeader = ({
const access = useUser();
const isAdmin =
decisionProfileId &&
access.getPermissionsForProfile(decisionProfileId).admin;
access.getPermissionsForProfile(decisionProfileId).decisions.admin;

return (
<header className="grid grid-cols-[auto_1fr_auto] items-center border-b bg-white p-2 px-6 sm:grid-cols-3 md:py-3">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const updateDecisionInstance = async ({
profileId,
});

assertAccess({ profile: permission.ADMIN }, profileUser?.roles ?? []);
assertAccess({ decisions: permission.ADMIN }, profileUser?.roles ?? []);

// Validate proposalTemplate is a structurally valid JSON Schema before persisting
if (proposalTemplate !== undefined) {
Expand Down
89 changes: 89 additions & 0 deletions tests/e2e/tests/decision-settings-permissions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
createDecisionInstance,
createOrganization,
getSeededTemplate,
grantDecisionProfileAccess,
} from '@op/test';

import {
TEST_USER_DEFAULT_PASSWORD,
authenticateAsUser,
expect,
test,
} from '../fixtures/index.js';

test.describe('Decision Settings Permissions', () => {
test('settings button is only visible to admin users', async ({
browser,
org,
supabaseAdmin,
}) => {
// 1. Get the seeded decision process template
const template = await getSeededTemplate();

// 2. Create a decision instance
const instance = await createDecisionInstance({
processId: template.id,
ownerProfileId: org.organizationProfile.id,
authUserId: org.adminUser.authUserId,
email: org.adminUser.email,
schema: template.processSchema,
});

// 3. Create a member user with org membership, then grant decision profile access
const memberOrg = await createOrganization({
testId: `settings-perm-${Date.now()}`,
supabaseAdmin,
users: { admin: 1, member: 0 },
});
const memberUser = memberOrg.adminUser;

await grantDecisionProfileAccess({
profileId: instance.profileId,
authUserId: memberUser.authUserId,
email: memberUser.email,
isAdmin: false,
});

// 4. Authenticate as the member user and navigate to the decision page
const memberContext = await browser.newContext();
const memberPage = await memberContext.newPage();
await authenticateAsUser(memberPage, {
email: memberUser.email,
password: TEST_USER_DEFAULT_PASSWORD,
});

await memberPage.goto(`/en/decisions/${instance.slug}`, {
waitUntil: 'networkidle',
});

await expect(
memberPage.getByRole('heading', { name: instance.name }),
).toBeVisible({ timeout: 15000 });

// 5. Assert the Settings button is NOT visible to the member
const settingsLink = memberPage.getByRole('link', { name: /Settings/ });
await expect(settingsLink).not.toBeVisible();

// 6. Authenticate as the admin user and verify Settings IS visible
const adminContext = await browser.newContext();
const adminPage = await adminContext.newPage();
await authenticateAsUser(adminPage, {
email: org.adminUser.email,
password: TEST_USER_DEFAULT_PASSWORD,
});

await adminPage.goto(`/en/decisions/${instance.slug}`, {
waitUntil: 'networkidle',
});

await expect(
adminPage.getByRole('heading', { name: instance.name }),
).toBeVisible({ timeout: 15000 });

const adminSettingsLink = adminPage.getByRole('link', {
name: /Settings/,
});
await expect(adminSettingsLink).toBeVisible({ timeout: 5000 });
});
});