Skip to content

Commit dc608e6

Browse files
committed
test(e2e): policies menu visibility follows delegated group rules
Verify that: - Policies nav item appears for a group subadmin when the instance admin has created a group-scoped rule with allowChildOverride:true. - The policy card is visible inside the workbench and the Configure button opens the setting dialog with an enabled Create rule button. - Clicking Create rule opens the scope-selector modal (create policy dialog). - After the admin removes the group rule, a page reload hides the nav item. All admin operations run via OCS API (no admin browser session), keeping the test fast. A single group-admin browser session covers both phases. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent b417091 commit dc608e6

1 file changed

Lines changed: 205 additions & 0 deletions

File tree

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
/**
7+
* Scenario: Policies menu visibility follows delegated group rules.
8+
*
9+
* 1. (API) Instance admin creates a group policy for GROUP_ID with
10+
* allowChildOverride:true, so the group admin can manage rules.
11+
* 2. (Browser) Log in as group admin → "Policies" nav item must be visible.
12+
* 3. (Browser) Navigate to Policies → see the editable policy card.
13+
* 4. (Browser) Click "Configure" → setting dialog opens.
14+
* 5. (Browser) Click "Create rule" inside dialog → scope-selector dialog opens.
15+
* 6. (API) Instance admin deletes the group policy.
16+
* 7. (Browser) Reload as group admin → "Policies" nav item must be hidden.
17+
*
18+
* All admin-side operations are performed via the OCS API so no admin browser
19+
* session is needed, keeping the test as fast as possible.
20+
*/
21+
22+
import { expect, request, test, type APIRequestContext } from '@playwright/test'
23+
import { login } from '../support/nc-login'
24+
import {
25+
ensureGroupExists,
26+
ensureSubadminOfGroup,
27+
ensureUserExists,
28+
ensureUserInGroup,
29+
} from '../support/nc-provisioning'
30+
31+
// One serial block: a single browser session for the group admin
32+
// across both phases avoids repeated login overhead.
33+
test.describe.configure({ mode: 'serial', retries: 0, timeout: 90000 })
34+
35+
const ADMIN_USER = process.env.NEXTCLOUD_ADMIN_USER ?? 'admin'
36+
const ADMIN_PASSWORD = process.env.NEXTCLOUD_ADMIN_PASSWORD ?? 'admin'
37+
const GROUP_ID = 'policy-menu-visibility-group'
38+
const GROUP_ADMIN = 'policy-menu-visibility-admin'
39+
const GROUP_ADMIN_PASSWORD = '123456'
40+
41+
// signature_flow: well-known valid values; "Signing order" UI title.
42+
// Using a system type so we don't need to set up complex JSON values (add_footer).
43+
const POLICY_KEY = 'signature_flow'
44+
45+
// ─── Admin API helpers (no browser needed) ────────────────────────────────────
46+
47+
async function makeAdminContext(): Promise<APIRequestContext> {
48+
return request.newContext({
49+
baseURL: process.env.PLAYWRIGHT_BASE_URL ?? 'https://localhost',
50+
ignoreHTTPSErrors: true,
51+
extraHTTPHeaders: {
52+
'OCS-ApiRequest': 'true',
53+
Accept: 'application/json',
54+
Authorization: 'Basic ' + Buffer.from(`${ADMIN_USER}:${ADMIN_PASSWORD}`).toString('base64'),
55+
'Content-Type': 'application/json',
56+
},
57+
})
58+
}
59+
60+
/**
61+
* POST /policies/system/{key} — establish the instance-wide default and allow
62+
* group admins to override it (allowChildOverride: true).
63+
*/
64+
async function setSystemPolicy(
65+
ctx: APIRequestContext,
66+
value: string | null,
67+
allowChildOverride: boolean,
68+
): Promise<void> {
69+
const resp = await ctx.post(
70+
`./ocs/v2.php/apps/libresign/api/v1/policies/system/${POLICY_KEY}`,
71+
{ data: { value, allowChildOverride }, failOnStatusCode: false },
72+
)
73+
expect(resp.status(), `setSystemPolicy: expected 200 but got ${resp.status()}`).toBe(200)
74+
}
75+
76+
/**
77+
* PUT /policies/group/{group}/{key} — create/update a rule scoped to GROUP_ID.
78+
* This increments groupCount in effective-policies so the menu visibility check
79+
* passes in Settings.vue.
80+
*/
81+
async function setGroupPolicy(
82+
ctx: APIRequestContext,
83+
value: string,
84+
allowChildOverride: boolean,
85+
): Promise<void> {
86+
const resp = await ctx.put(
87+
`./ocs/v2.php/apps/libresign/api/v1/policies/group/${GROUP_ID}/${POLICY_KEY}`,
88+
{ data: { value, allowChildOverride }, failOnStatusCode: false },
89+
)
90+
expect(resp.status(), `setGroupPolicy: expected 200 but got ${resp.status()}`).toBe(200)
91+
}
92+
93+
/**
94+
* DELETE /policies/group/{group}/{key} — remove the rule so groupCount drops to 0
95+
* and the Policies nav item disappears for the group admin.
96+
*/
97+
async function deleteGroupPolicy(ctx: APIRequestContext): Promise<void> {
98+
await ctx.delete(
99+
`./ocs/v2.php/apps/libresign/api/v1/policies/group/${GROUP_ID}/${POLICY_KEY}`,
100+
{ failOnStatusCode: false },
101+
)
102+
}
103+
104+
// ─── Test ─────────────────────────────────────────────────────────────────────
105+
106+
test('policies nav item appears for group admin when a group rule exists, and hides when removed', async ({ page }) => {
107+
const adminCtx = await makeAdminContext()
108+
109+
try {
110+
// ── 0. Provision users/groups (idempotent; safe to call on every run) ──
111+
112+
await ensureUserExists(page.request, GROUP_ADMIN, GROUP_ADMIN_PASSWORD)
113+
await ensureGroupExists(page.request, GROUP_ID)
114+
await ensureUserInGroup(page.request, GROUP_ADMIN, GROUP_ID)
115+
// subadmin role → can_manage_group_policies: true in initial state
116+
await ensureSubadminOfGroup(page.request, GROUP_ADMIN, GROUP_ID)
117+
118+
// ── 1. Admin: create group policy ─────────────────────────────────────
119+
//
120+
// System policy must allow child overrides so the workbench unlocks the
121+
// "Create rule" button for the group admin.
122+
await setSystemPolicy(adminCtx, 'parallel', true)
123+
// Group-scoped rule → groupCount becomes ≥ 1 in the effective-policies
124+
// API response seen by the group admin.
125+
await setGroupPolicy(adminCtx, 'ordered_numeric', true)
126+
127+
// ── 2. Log in as group admin ───────────────────────────────────────────
128+
129+
await login(page.request, GROUP_ADMIN, GROUP_ADMIN_PASSWORD)
130+
131+
// Preferences page mounts Preferences.vue which calls fetchEffectivePolicies()
132+
// on onMounted, populating the Pinia store that Settings.vue reads reactively.
133+
await page.goto('./apps/libresign/f/preferences')
134+
135+
// ── 3. "Policies" must appear in the settings sidebar ─────────────────
136+
137+
const policiesNavItem = page.getByRole('link', { name: 'Policies' })
138+
await expect(policiesNavItem, 'Policies link should be visible when a delegated group rule exists').toBeVisible({ timeout: 20000 })
139+
140+
// ── 4. Navigate to the Policies page ──────────────────────────────────
141+
142+
await policiesNavItem.click()
143+
await expect(page).toHaveURL(/\/f\/policies/, { timeout: 10000 })
144+
145+
// ── 5. The editable policy card must be visible in the workbench ──────
146+
//
147+
// In group-admin viewMode, only policies satisfying
148+
// groupCount > 0 AND editableByCurrentActor === true
149+
// are rendered. "Signing order" (signature_flow) matches because we set
150+
// system allowChildOverride:true and a group rule for GROUP_ID.
151+
152+
const configureButton = page
153+
.getByRole('button', { name: /Configure/i })
154+
.first()
155+
await expect(configureButton, 'At least one Configure button should be visible for the group admin').toBeVisible({ timeout: 15000 })
156+
157+
// ── 6. Open the setting dialog ("Signing order") ──────────────────────
158+
159+
await configureButton.click()
160+
161+
const settingDialog = page.getByRole('dialog', { name: /Signing order/i })
162+
await expect(settingDialog, '"Signing order" dialog should open on click').toBeVisible({ timeout: 10000 })
163+
164+
// ── 7. "Create rule" button must be available inside the dialog ───────
165+
166+
const createRuleButton = settingDialog.getByRole('button', { name: /Create rule/i })
167+
await expect(createRuleButton, '"Create rule" button should be enabled in the policy dialog').toBeVisible()
168+
await expect(createRuleButton).toBeEnabled()
169+
170+
// ── 8. Clicking "Create rule" opens the scope-selector ("create policy modal") ──
171+
172+
await createRuleButton.click()
173+
174+
// The scope-selector dialog title is "What do you want to create?"
175+
// (falls back to "Create rule" if the workbench skips the selector)
176+
const createPolicyDialog = page
177+
.getByRole('dialog', { name: /What do you want to create\?|Create rule/i })
178+
.last()
179+
await expect(createPolicyDialog, 'Create-policy modal should appear after clicking Create rule').toBeVisible({ timeout: 10000 })
180+
181+
// Close with Escape — no actual rule is created
182+
await page.keyboard.press('Escape')
183+
await expect(createPolicyDialog).toBeHidden({ timeout: 5000 })
184+
185+
// ── 9. Admin: remove the group policy ─────────────────────────────────
186+
187+
await deleteGroupPolicy(adminCtx)
188+
189+
// ── 10. Reload as group admin to refresh effective-policies state ──────
190+
//
191+
// A full navigation re-triggers fetchEffectivePolicies() in Preferences.vue,
192+
// causing Settings.vue's hasDelegatedPolicies computed to update reactively.
193+
194+
await page.goto('./apps/libresign/f/preferences')
195+
196+
// ── 11. "Policies" must no longer appear in the settings sidebar ───────
197+
198+
await expect(page.getByRole('link', { name: 'Policies' }), 'Policies link should be gone after the group rule is removed').toBeHidden({ timeout: 20000 })
199+
} finally {
200+
// Always restore the environment so other tests are not affected.
201+
await deleteGroupPolicy(adminCtx).catch(() => {})
202+
await setSystemPolicy(adminCtx, null, true).catch(() => {})
203+
await adminCtx.dispose()
204+
}
205+
})

0 commit comments

Comments
 (0)