Skip to content

feat(auth/frontend): migrate UI from roles to group-membership authorization#914

Open
cristim wants to merge 2 commits into
feat/multicloud-web-frontendfrom
feat/907-group-only-authz-frontend
Open

feat(auth/frontend): migrate UI from roles to group-membership authorization#914
cristim wants to merge 2 commits into
feat/multicloud-web-frontendfrom
feat/907-group-only-authz-frontend

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented Jun 1, 2026

Summary

Frontend half of #907 (backend: #912). Both PRs must land together -- #912 drops user.role from the API response and the current UI gates on it.

  • Replace all user.role === 'admin' checks with isAdmin() (group-membership-based)
  • isAdmin() now returns true when the current user belongs to the Administrators group (UUID 00000000-0000-5000-8000-000000000001, seeded by migration 000057 in feat(auth): group-membership-only authorization, remove roles, require >=1 group (closes #907) #912)
  • Remove the role selector from the Create/Edit User modal; replace with a required group multi-select (>=1 group enforced by JS validation + HTML required attr, mirroring the backend DB CHECK constraint)
  • Drop bulkChangeRole (role concept no longer exists)
  • Update api/types.ts: User, APIUser lose the role field; CreateUserRequest makes groups required
  • Update all 65 test suites to use group-array fixtures instead of role strings; permissions tests rewritten for the group-based model
  • canAccess() for non-admin users returns false pending a future /me/permissions endpoint (deferred); backend still enforces all permissions

What #912 changed on the backend

  • users.role and sessions.role columns dropped; authorization is purely group-derived via HasPermissionAPI
  • GET /api/auth/me now returns groups: string[] instead of role: string
  • POST /api/users and PUT /api/users/:id require groups length >= 1 (400 otherwise)

Test plan

  • npm test (65 suites, 2196+ tests): all green
  • npm run build: compiles without errors
  • Login as Administrators-group member: Admin badge visible, admin nav visible
  • Login as Standard Users member: no Admin badge, no admin nav
  • Create User form: submitting with zero groups shows validation error
  • Create User form: submitting with one or more groups succeeds

Frontend half of #907 (backend: #912)

🤖 Generated with claude-flow

Summary by CodeRabbit

Release Notes

  • New Features

    • Authorization system now based on group membership for more granular permission control
  • Changes

    • User creation and editing interfaces updated: role selector replaced with required multi-select groups field
    • User list updated to reflect group-based information instead of individual roles
    • Administrative privileges now determined by group membership

…ization

Pairs with #912 (backend). The API no longer returns user.role; all
authorization derives from group membership. Administrators group UUID
(00000000-0000-5000-8000-000000000001) replaces the admin-role check.

Key changes:
- api/types.ts: User/APIUser drop role; CreateUserRequest groups required (>=1)
- permissions.ts: isAdmin() checks Administrators group membership instead of
  role; canAccess() returns true only for Administrators-group members until the
  /me/permissions endpoint lands (deferred follow-up)
- auth.ts, history.ts: replace user.role === 'admin' with isAdmin()
- index.html: remove role selector from user modal; groups multi-select is now
  required (HTML required attr + JS validation: at least 1 group required)
- users/userModals.ts: saveUser rejects zero-group submissions with a clear
  validation message mirroring the backend DB CHECK constraint
- users/userList.ts: admin count uses ADMINISTRATORS_GROUP_ID membership;
  effectivePermissions derives from group permissions only (no role defaults)
- users/handlers.ts, userActions.ts: remove bulkChangeRole (role concept gone)
- users/filters.ts: role filter maps to Administrators group membership
- All test fixtures updated from role strings to groups arrays
- Permissions tests rewritten for group-based model with new isAdmin tests

Frontend half of #907 (backend: #912). Both PRs must land together.
@cristim cristim added triaged Item has been triaged priority/p2 Backlog-worthy severity/high Significant harm urgency/this-sprint Within the current sprint impact/all-users Affects every user effort/m Days type/security Security finding labels Jun 1, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

Warning

Review limit reached

@cristim, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 19 minutes and 51 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f23ad1f6-0487-497e-a97f-c90f8a775078

📥 Commits

Reviewing files that changed from the base of the PR and between f7f4347 and e2e9e8d.

📒 Files selected for processing (10)
  • frontend/src/__tests__/auth.test.ts
  • frontend/src/__tests__/history-approval-queue.test.ts
  • frontend/src/__tests__/history-approve-button.test.ts
  • frontend/src/__tests__/history-cancel-button.test.ts
  • frontend/src/__tests__/history-retry-button.test.ts
  • frontend/src/__tests__/plans-permissions.test.ts
  • frontend/src/__tests__/plans.test.ts
  • frontend/src/__tests__/recommendations-enabled-providers.test.ts
  • frontend/src/__tests__/recommendations-permissions.test.ts
  • frontend/src/__tests__/state.test.ts
📝 Walkthrough

Walkthrough

This PR removes user role fields and replaces them with group-based authorization across the entire frontend. API type contracts are updated, the permissions module implements group membership checks, UI gates use isAdmin(), user management forms enforce required groups, and all tests are updated to reflect the new authorization model.

Changes

Frontend Authorization Migration: Role to Group-Based Access Control

Layer / File(s) Summary
Authorization contract and permissions system
frontend/src/api/types.ts, frontend/src/permissions.ts, frontend/src/__tests__/permissions.test.ts
User/APIUser remove role field; CreateUserRequest requires groups: string[]. Permissions module adds ADMINISTRATORS_GROUP_ID constant; isAdmin() checks group membership; canAccess() becomes admin-only stub. Tests verify group-based admin detection including edge cases (empty groups, null user, non-array groups).
Session/auth and history UI gating
frontend/src/auth.ts, frontend/src/history.ts, frontend/src/__tests__/auth.test.ts, frontend/src/__tests__/history-*.test.ts
updateUserUI() uses permissionsIsAdmin() to gate admin badges/elements. History action buttons (canCancelPendingRow, canApprovePendingRow, canRetryFailedRow) import and use isAdmin() instead of role comparisons. Auth and history tests mocked with group-based users.
User management forms and modals
frontend/src/index.html, frontend/src/users/userModals.ts, frontend/src/__tests__/html.test.ts, frontend/src/__tests__/users.test.ts (modals section)
HTML form removes role selector, replaces with required multi-select groups field. userModals.ts adds required-group validation (≥1 group) before save, removes role field reading/submission. Form tests verify modal DOM structure, required-group validation rejection, and API payloads omit role.
User list, filtering, and admin statistics
frontend/src/users/userList.ts, frontend/src/users/filters.ts, frontend/src/__tests__/users.test.ts (list/filter sections)
renderUserStats counts admins via groups.includes(ADMINISTRATORS_GROUP_ID). Users table removes Role column and per-row role badges; adds group badge rendering with UUID fallback. applyFilters role filtering derives admin from group membership. Tests verify admin counting, role-column removal, group-badge display, and group/role/MFA combined filtering.
Bulk role action removal
frontend/src/users.ts, frontend/src/users/index.ts, frontend/src/users/handlers.ts, frontend/src/users/userActions.ts, frontend/src/__tests__/users.test.ts (handlers section)
Removes bulkChangeRole re-export/function. Handler stops wiring bulk-role-btn (left as intentional no-op). userActions.ts ensures synthetic "current" user has validated groups array. Tests assert function not exported and button unwired.
Test fixtures and feature-specific updates
frontend/src/__tests__/*.test.ts
State, group, auth-mfa, a11y, api, allowed-accounts, plans, recommendations, RI Exchange, settings, and XSS tests updated with groups fixtures. Integration tests align user data with group-based admin model. Permission tests replace role-based expectations with group membership checks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • LeanerCloud/CUDly#907: Directly implements the frontend goal to remove role field and derive admin/permissions from the Administrators group, with group-membership-based RBAC throughout.
  • LeanerCloud/CUDly#913: Frontend follow-up task to remove role from UI, forms, and tests in favor of group-based authorization (this PR's primary implementation).

Possibly related PRs

  • LeanerCloud/CUDly#587: Updates mocked ADMIN user shape in allowed-accounts.test.ts from role to groups (same test file modified by both PRs).
  • LeanerCloud/CUDly#452: Modifies isAdmin()/canAccess() in permissions.ts for UX-only authorization gating; this PR refactors those same permission helpers to group-based logic.

Suggested labels

type/feat


🐰 From the warren, a proclamation fair:
The role field fades, but groups are there!
No more strings of "admin"—membership reigns,
Through UUID checks in permission's veins!
Frontend auth now springs, lean and true.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: migrating the frontend UI from role-based to group-membership-based authorization, which is the primary objective of this pull request.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/907-group-only-authz-frontend

Comment @coderabbitai help to get the list of available commands and usage tips.

@cristim
Copy link
Copy Markdown
Member Author

cristim commented Jun 1, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (10)
frontend/src/__tests__/plans-permissions.test.ts (1)

75-79: ⚡ Quick win

Import the admin group constant instead of hardcoding the UUID.

The mockUser helper hardcodes the Administrators group UUID. Import ADMINISTRATORS_GROUP_ID from frontend/src/permissions.ts to maintain consistency with the authorization contract layer.

♻️ Suggested refactor
+import { ADMINISTRATORS_GROUP_ID } from '../permissions';
+
 /**
  * Plans page permission-gating tests for issue `#365`.
 const mockUser = (role: string | null) => {
   (state.getCurrentUser as jest.Mock).mockReturnValue(
-    role === null ? null : { id: 'u', email: 'u@example.com', groups: role === 'admin' ? ['00000000-0000-5000-8000-000000000001'] : [] },
+    role === null ? null : { id: 'u', email: 'u@example.com', groups: role === 'admin' ? [ADMINISTRATORS_GROUP_ID] : [] },
   );
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/__tests__/plans-permissions.test.ts` around lines 75 - 79, The
test helper mockUser currently hardcodes the Administrators group UUID; update
it to import and use ADMINISTRATORS_GROUP_ID from frontend/src/permissions.ts
instead of the literal '00000000-0000-5000-8000-000000000001'. Modify the
mockUser implementation that calls (state.getCurrentUser as
jest.Mock).mockReturnValue(...) to set groups to role === 'admin' ?
[ADMINISTRATORS_GROUP_ID] : [], and add the import for ADMINISTRATORS_GROUP_ID
at the top of the test file.
frontend/src/__tests__/recommendations-permissions.test.ts (1)

67-71: ⚡ Quick win

Import the admin group constant instead of hardcoding the UUID.

The mockUser helper hardcodes the Administrators group UUID. Import ADMINISTRATORS_GROUP_ID from frontend/src/permissions.ts for consistency.

♻️ Suggested refactor
+import { ADMINISTRATORS_GROUP_ID } from '../permissions';
+
 /**
  * Recommendations permission gating tests.
 const mockUser = (role: string | null) => {
   (state.getCurrentUser as jest.Mock).mockReturnValue(
-    role === null ? null : { id: 'u', email: 'u@example.com', groups: role === 'admin' ? ['00000000-0000-5000-8000-000000000001'] : [] },
+    role === null ? null : { id: 'u', email: 'u@example.com', groups: role === 'admin' ? [ADMINISTRATORS_GROUP_ID] : [] },
   );
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/__tests__/recommendations-permissions.test.ts` around lines 67 -
71, Replace the hardcoded Administrators UUID in the mockUser helper with the
exported constant ADMINISTRATORS_GROUP_ID: import ADMINISTRATORS_GROUP_ID from
the permissions module, then use ADMINISTRATORS_GROUP_ID in the groups array
when role === 'admin' (the mockUser function that calls state.getCurrentUser
should reference ADMINISTRATORS_GROUP_ID instead of the literal UUID).
frontend/src/__tests__/state.test.ts (1)

37-37: ⚡ Quick win

Import the admin group constant instead of hardcoding the UUID.

The Administrators group UUID '00000000-0000-5000-8000-000000000001' is hardcoded here and appears in multiple test files. According to the layer context, this constant is defined as ADMINISTRATORS_GROUP_ID in frontend/src/permissions.ts. Import and use that constant to maintain a single source of truth and simplify future changes.

♻️ Suggested refactor
+import { ADMINISTRATORS_GROUP_ID } from '../permissions';
+
 /**
  * State module tests
  */

Then replace the hardcoded UUIDs:

-      const user = { id: '123', email: 'test@example.com', groups: ['00000000-0000-5000-8000-000000000001'] };
+      const user = { id: '123', email: 'test@example.com', groups: [ADMINISTRATORS_GROUP_ID] };
       setCurrentUser(user);
-      setCurrentUser({ id: '123', email: 'test@example.com', groups: ['00000000-0000-5000-8000-000000000001'] });
+      setCurrentUser({ id: '123', email: 'test@example.com', groups: [ADMINISTRATORS_GROUP_ID] });
       setCurrentUser(null);

Apply the same pattern to line 197.

Also applies to: 43-43, 197-197

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/__tests__/state.test.ts` at line 37, Replace hardcoded
Administrators UUIDs with the shared constant: import ADMINISTRATORS_GROUP_ID
from frontend/src/permissions.ts and use it in the test user/group arrays
instead of the literal '00000000-0000-5000-8000-000000000001'; update every
occurrence in this test file (the user constant and the other instance noted) so
the tests reference ADMINISTRATORS_GROUP_ID rather than the hardcoded string.
frontend/src/__tests__/plans.test.ts (1)

52-52: ⚡ Quick win

Import the admin group constant instead of hardcoding the UUID.

The Administrators group UUID should be imported from frontend/src/permissions.ts as ADMINISTRATORS_GROUP_ID rather than hardcoded here. This maintains consistency with the authorization contract layer and simplifies future maintenance.

♻️ Suggested refactor

Add the import at the top of the file:

+import { ADMINISTRATORS_GROUP_ID } from '../permissions';
+
 /**
  * Plans module tests
  */

Then use the constant:

-  getCurrentUser: jest.fn().mockReturnValue({ id: 'u-admin', email: 'admin@example.com', groups: ['00000000-0000-5000-8000-000000000001'] }),
+  getCurrentUser: jest.fn().mockReturnValue({ id: 'u-admin', email: 'admin@example.com', groups: [ADMINISTRATORS_GROUP_ID] }),
 }));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/__tests__/plans.test.ts` at line 52, Replace the hardcoded
Administrators UUID in the getCurrentUser jest mock with the exported
ADMINISTRATORS_GROUP_ID constant: add an import for ADMINISTRATORS_GROUP_ID from
frontend/src/permissions.ts at the top of the test file and update the
getCurrentUser: jest.fn().mockReturnValue({ id: 'u-admin', email:
'admin@example.com', groups: [/* replace value */] }) to use
ADMINISTRATORS_GROUP_ID in the groups array so the test references the shared
authorization constant.
frontend/src/__tests__/recommendations-enabled-providers.test.ts (1)

65-65: ⚡ Quick win

Import the admin group constant instead of hardcoding the UUID.

Replace the hardcoded Administrators group UUID with ADMINISTRATORS_GROUP_ID imported from frontend/src/permissions.ts to maintain consistency across the codebase.

♻️ Suggested refactor
+import { ADMINISTRATORS_GROUP_ID } from '../permissions';
+
 /**
  * Issue `#463`: the Settings → General → Enabled Providers toggles must
-  getCurrentUser: jest.fn().mockReturnValue({ id: 'u-admin', email: 'admin@example.com', groups: ['00000000-0000-5000-8000-000000000001'] }),
+  getCurrentUser: jest.fn().mockReturnValue({ id: 'u-admin', email: 'admin@example.com', groups: [ADMINISTRATORS_GROUP_ID] }),
 }));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/__tests__/recommendations-enabled-providers.test.ts` at line 65,
The test currently hardcodes the Administrators group UUID in the jest mock for
getCurrentUser; replace the literal '00000000-0000-5000-8000-000000000001' with
the exported ADMINISTRATORS_GROUP_ID imported from frontend/src/permissions.ts,
and update the imports at the top of
frontend/src/__tests__/recommendations-enabled-providers.test.ts to include
ADMINISTRATORS_GROUP_ID so the getCurrentUser mock uses that constant.
frontend/src/__tests__/history-approval-queue.test.ts (1)

71-71: ⚡ Quick win

Consider importing ADMINISTRATORS_GROUP_ID constant instead of hardcoding the UUID.

The admin group UUID '00000000-0000-5000-8000-000000000001' is hardcoded here and in multiple other test files. According to the review stack context, permissions.ts exports an ADMINISTRATORS_GROUP_ID constant. Importing and using that constant would improve maintainability.

♻️ Proposed refactor

Add the import at the top of the file:

 import { getCurrentUser } from '../state';
 import { getAccountName } from '../recommendations';
+import { ADMINISTRATORS_GROUP_ID } from '../permissions';

Then update the constant:

-const ADMIN_USER = { id: 'admin-uuid', email: 'admin@example.com', groups: ['00000000-0000-5000-8000-000000000001'] };
+const ADMIN_USER = { id: 'admin-uuid', email: 'admin@example.com', groups: [ADMINISTRATORS_GROUP_ID] };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/__tests__/history-approval-queue.test.ts` at line 71, Replace
the hardcoded admin group UUID in the test's ADMIN_USER definition with the
exported constant ADMINISTRATORS_GROUP_ID from permissions.ts: import
ADMINISTRATORS_GROUP_ID at the top of the test file and use that symbol in the
groups array for ADMIN_USER (referencing ADMIN_USER and ADMINISTRATORS_GROUP_ID
to locate the change).
frontend/src/__tests__/history-approve-button.test.ts (1)

67-68: ⚡ Quick win

Consider importing ADMINISTRATORS_GROUP_ID constant instead of hardcoding the UUID.

The admin group UUID '00000000-0000-5000-8000-000000000001' is hardcoded here and in multiple other test files. According to the review stack context, permissions.ts exports an ADMINISTRATORS_GROUP_ID constant. Importing and using that constant would improve maintainability.

♻️ Proposed refactor

Add the import at the top of the file:

 import { confirmDialog } from '../confirmDialog';
 import { showToast } from '../toast';
 import { getCurrentUser } from '../state';
+import { ADMINISTRATORS_GROUP_ID } from '../permissions';

Then update the constant:

-const ADMIN_USER = { id: 'admin-uuid', email: 'admin@example.com', groups: ['00000000-0000-5000-8000-000000000001'] };
+const ADMIN_USER = { id: 'admin-uuid', email: 'admin@example.com', groups: [ADMINISTRATORS_GROUP_ID] };
 const REG_USER = { id: 'user-uuid', email: 'user@example.com', groups: [] };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/__tests__/history-approve-button.test.ts` around lines 67 - 68,
Replace the hardcoded admin group UUID in the test by importing and using the
ADMINISTRATORS_GROUP_ID constant from permissions.ts; update the ADMIN_USER
definition (the symbol ADMIN_USER in this test) to include
[ADMINISTRATORS_GROUP_ID] instead of the string literal, and ensure the import
for ADMINISTRATORS_GROUP_ID is added at the top of the test file so other tests
can reuse the same constant.
frontend/src/__tests__/history-cancel-button.test.ts (1)

64-65: ⚡ Quick win

Consider importing ADMINISTRATORS_GROUP_ID constant instead of hardcoding the UUID.

The admin group UUID '00000000-0000-5000-8000-000000000001' is hardcoded here and in multiple other test files. According to the review stack context, permissions.ts exports an ADMINISTRATORS_GROUP_ID constant. Importing and using that constant would improve maintainability.

♻️ Proposed refactor

Add the import at the top of the file:

 import { confirmDialog } from '../confirmDialog';
 import { showToast } from '../toast';
 import { getCurrentUser } from '../state';
+import { ADMINISTRATORS_GROUP_ID } from '../permissions';

Then update the constant:

-const ADMIN_USER = { id: 'admin-uuid', email: 'admin@example.com', groups: ['00000000-0000-5000-8000-000000000001'] };
+const ADMIN_USER = { id: 'admin-uuid', email: 'admin@example.com', groups: [ADMINISTRATORS_GROUP_ID] };
 const REG_USER = { id: 'user-uuid', email: 'user@example.com', groups: [] };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/__tests__/history-cancel-button.test.ts` around lines 64 - 65,
Replace the hardcoded admin group UUID in the test user fixtures with the
exported ADMINISTRATORS_GROUP_ID constant: add an import for
ADMINISTRATORS_GROUP_ID from the permissions module at the top of the test file
and update ADMIN_USER (and any similar occurrences in this file) to use
ADMINISTRATORS_GROUP_ID in its groups array instead of the literal
'00000000-0000-5000-8000-000000000001'; keep REG_USER unchanged.
frontend/src/__tests__/history-retry-button.test.ts (1)

73-74: ⚡ Quick win

Consider importing ADMINISTRATORS_GROUP_ID constant instead of hardcoding the UUID.

The admin group UUID '00000000-0000-5000-8000-000000000001' is hardcoded here and in multiple other test files. According to the review stack context, permissions.ts exports an ADMINISTRATORS_GROUP_ID constant. Importing and using that constant would improve maintainability.

♻️ Proposed refactor

Add the import at the top of the file:

 import { confirmDialog } from '../confirmDialog';
 import { showToast } from '../toast';
 import { getCurrentUser } from '../state';
+import { ADMINISTRATORS_GROUP_ID } from '../permissions';

Then update the constant:

-const ADMIN_USER = { id: 'admin-uuid', email: 'admin@example.com', groups: ['00000000-0000-5000-8000-000000000001'] };
+const ADMIN_USER = { id: 'admin-uuid', email: 'admin@example.com', groups: [ADMINISTRATORS_GROUP_ID] };
 const REG_USER = { id: 'user-uuid', email: 'user@example.com', groups: [] };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/__tests__/history-retry-button.test.ts` around lines 73 - 74,
Replace the hardcoded admin group UUID in the test's ADMIN_USER definition with
the exported ADMINISTRATORS_GROUP_ID constant from permissions.ts: add an import
for ADMINISTRATORS_GROUP_ID at the top of the test file and use that symbol in
the ADMIN_USER.groups array instead of the literal
'00000000-0000-5000-8000-000000000001' to centralize the identifier and avoid
duplication.
frontend/src/__tests__/auth.test.ts (1)

404-408: ⚡ Quick win

Consider importing ADMINISTRATORS_GROUP_ID constant instead of hardcoding the UUID.

The admin group UUID '00000000-0000-5000-8000-000000000001' is hardcoded in multiple test files. According to the review stack context, permissions.ts exports an ADMINISTRATORS_GROUP_ID constant. Importing and using that constant would improve maintainability—if the UUID changes, tests will automatically stay in sync.

♻️ Proposed refactor

At the top of the file, add the import:

 import * as api from '../api';
 import * as state from '../state';
+import { ADMINISTRATORS_GROUP_ID } from '../permissions';

Then update the admin mock fixture:

       (state.getCurrentUser as jest.Mock).mockReturnValue({
         id: 'admin-1',
         email: 'admin@example.com',
-        groups: ['00000000-0000-5000-8000-000000000001']
+        groups: [ADMINISTRATORS_GROUP_ID]
       });

Apply the same pattern to all other admin fixture mocks in this file.

Also applies to: 417-421, 439-443, 454-458, 469-473, 483-487, 522-526

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/__tests__/auth.test.ts` around lines 404 - 408, Replace
hardcoded admin UUIDs in the test mocks with the exported
ADMINISTRATORS_GROUP_ID constant: add an import for ADMINISTRATORS_GROUP_ID from
the module that exports it (permissions.ts) at the top of the test file, then
update the mocked user/group fixtures (the places where (state.getCurrentUser as
jest.Mock).mockReturnValue or similar admin mock arrays are set) to use
ADMINISTRATORS_GROUP_ID instead of the literal
'00000000-0000-5000-8000-000000000001'; apply the same change to all other admin
fixture mocks in this file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@frontend/src/__tests__/auth.test.ts`:
- Around line 404-408: Replace hardcoded admin UUIDs in the test mocks with the
exported ADMINISTRATORS_GROUP_ID constant: add an import for
ADMINISTRATORS_GROUP_ID from the module that exports it (permissions.ts) at the
top of the test file, then update the mocked user/group fixtures (the places
where (state.getCurrentUser as jest.Mock).mockReturnValue or similar admin mock
arrays are set) to use ADMINISTRATORS_GROUP_ID instead of the literal
'00000000-0000-5000-8000-000000000001'; apply the same change to all other admin
fixture mocks in this file.

In `@frontend/src/__tests__/history-approval-queue.test.ts`:
- Line 71: Replace the hardcoded admin group UUID in the test's ADMIN_USER
definition with the exported constant ADMINISTRATORS_GROUP_ID from
permissions.ts: import ADMINISTRATORS_GROUP_ID at the top of the test file and
use that symbol in the groups array for ADMIN_USER (referencing ADMIN_USER and
ADMINISTRATORS_GROUP_ID to locate the change).

In `@frontend/src/__tests__/history-approve-button.test.ts`:
- Around line 67-68: Replace the hardcoded admin group UUID in the test by
importing and using the ADMINISTRATORS_GROUP_ID constant from permissions.ts;
update the ADMIN_USER definition (the symbol ADMIN_USER in this test) to include
[ADMINISTRATORS_GROUP_ID] instead of the string literal, and ensure the import
for ADMINISTRATORS_GROUP_ID is added at the top of the test file so other tests
can reuse the same constant.

In `@frontend/src/__tests__/history-cancel-button.test.ts`:
- Around line 64-65: Replace the hardcoded admin group UUID in the test user
fixtures with the exported ADMINISTRATORS_GROUP_ID constant: add an import for
ADMINISTRATORS_GROUP_ID from the permissions module at the top of the test file
and update ADMIN_USER (and any similar occurrences in this file) to use
ADMINISTRATORS_GROUP_ID in its groups array instead of the literal
'00000000-0000-5000-8000-000000000001'; keep REG_USER unchanged.

In `@frontend/src/__tests__/history-retry-button.test.ts`:
- Around line 73-74: Replace the hardcoded admin group UUID in the test's
ADMIN_USER definition with the exported ADMINISTRATORS_GROUP_ID constant from
permissions.ts: add an import for ADMINISTRATORS_GROUP_ID at the top of the test
file and use that symbol in the ADMIN_USER.groups array instead of the literal
'00000000-0000-5000-8000-000000000001' to centralize the identifier and avoid
duplication.

In `@frontend/src/__tests__/plans-permissions.test.ts`:
- Around line 75-79: The test helper mockUser currently hardcodes the
Administrators group UUID; update it to import and use ADMINISTRATORS_GROUP_ID
from frontend/src/permissions.ts instead of the literal
'00000000-0000-5000-8000-000000000001'. Modify the mockUser implementation that
calls (state.getCurrentUser as jest.Mock).mockReturnValue(...) to set groups to
role === 'admin' ? [ADMINISTRATORS_GROUP_ID] : [], and add the import for
ADMINISTRATORS_GROUP_ID at the top of the test file.

In `@frontend/src/__tests__/plans.test.ts`:
- Line 52: Replace the hardcoded Administrators UUID in the getCurrentUser jest
mock with the exported ADMINISTRATORS_GROUP_ID constant: add an import for
ADMINISTRATORS_GROUP_ID from frontend/src/permissions.ts at the top of the test
file and update the getCurrentUser: jest.fn().mockReturnValue({ id: 'u-admin',
email: 'admin@example.com', groups: [/* replace value */] }) to use
ADMINISTRATORS_GROUP_ID in the groups array so the test references the shared
authorization constant.

In `@frontend/src/__tests__/recommendations-enabled-providers.test.ts`:
- Line 65: The test currently hardcodes the Administrators group UUID in the
jest mock for getCurrentUser; replace the literal
'00000000-0000-5000-8000-000000000001' with the exported ADMINISTRATORS_GROUP_ID
imported from frontend/src/permissions.ts, and update the imports at the top of
frontend/src/__tests__/recommendations-enabled-providers.test.ts to include
ADMINISTRATORS_GROUP_ID so the getCurrentUser mock uses that constant.

In `@frontend/src/__tests__/recommendations-permissions.test.ts`:
- Around line 67-71: Replace the hardcoded Administrators UUID in the mockUser
helper with the exported constant ADMINISTRATORS_GROUP_ID: import
ADMINISTRATORS_GROUP_ID from the permissions module, then use
ADMINISTRATORS_GROUP_ID in the groups array when role === 'admin' (the mockUser
function that calls state.getCurrentUser should reference
ADMINISTRATORS_GROUP_ID instead of the literal UUID).

In `@frontend/src/__tests__/state.test.ts`:
- Line 37: Replace hardcoded Administrators UUIDs with the shared constant:
import ADMINISTRATORS_GROUP_ID from frontend/src/permissions.ts and use it in
the test user/group arrays instead of the literal
'00000000-0000-5000-8000-000000000001'; update every occurrence in this test
file (the user constant and the other instance noted) so the tests reference
ADMINISTRATORS_GROUP_ID rather than the hardcoded string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5621bff8-99b7-41a4-a7c0-e781ac47e6dd

📥 Commits

Reviewing files that changed from the base of the PR and between 80c20d2 and f7f4347.

📒 Files selected for processing (36)
  • frontend/src/__tests__/a11y.test.ts
  • frontend/src/__tests__/allowed-accounts.test.ts
  • frontend/src/__tests__/api.test.ts
  • frontend/src/__tests__/auth-mfa-enroll.test.ts
  • frontend/src/__tests__/auth.test.ts
  • frontend/src/__tests__/groups.test.ts
  • frontend/src/__tests__/history-approval-queue.test.ts
  • frontend/src/__tests__/history-approve-button.test.ts
  • frontend/src/__tests__/history-cancel-button.test.ts
  • frontend/src/__tests__/history-retry-button.test.ts
  • frontend/src/__tests__/html.test.ts
  • frontend/src/__tests__/permissions.test.ts
  • frontend/src/__tests__/plans-permissions.test.ts
  • frontend/src/__tests__/plans-range-validation.test.ts
  • frontend/src/__tests__/plans.test.ts
  • frontend/src/__tests__/recommendations-enabled-providers.test.ts
  • frontend/src/__tests__/recommendations-permissions.test.ts
  • frontend/src/__tests__/recommendations.test.ts
  • frontend/src/__tests__/riexchange-permissions.test.ts
  • frontend/src/__tests__/riexchange.test.ts
  • frontend/src/__tests__/settings-permissions.test.ts
  • frontend/src/__tests__/state.test.ts
  • frontend/src/__tests__/users.test.ts
  • frontend/src/__tests__/xss-purchase-status.test.ts
  • frontend/src/api/types.ts
  • frontend/src/auth.ts
  • frontend/src/history.ts
  • frontend/src/index.html
  • frontend/src/permissions.ts
  • frontend/src/users.ts
  • frontend/src/users/filters.ts
  • frontend/src/users/handlers.ts
  • frontend/src/users/index.ts
  • frontend/src/users/userActions.ts
  • frontend/src/users/userList.ts
  • frontend/src/users/userModals.ts

…S_GROUP_ID constant

Replace literal '00000000-0000-5000-8000-000000000001' with imported
ADMINISTRATORS_GROUP_ID from permissions.ts in 8 test files where
top-level imports can reach the factory; leave the literal (with an
explanatory comment) in the 2 jest.mock() factories where permissions.ts
cannot be required due to a circular import through the mocked state
module (permissions.test.ts pins the UUID value as a regression guard).

Fixes CodeRabbit nitpicks on PR #914.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented Jun 1, 2026

Addressed all 10 CodeRabbit nitpicks from the 2026-06-01T20:19Z review (commit e2e9e8d).

Findings fixed (8/10): Added import { ADMINISTRATORS_GROUP_ID } from '../permissions' and replaced the hardcoded '00000000-0000-5000-8000-000000000001' literal in:

  • auth.test.ts
  • state.test.ts (2 occurrences)
  • plans-permissions.test.ts
  • recommendations-permissions.test.ts
  • history-approval-queue.test.ts
  • history-approve-button.test.ts
  • history-cancel-button.test.ts
  • history-retry-button.test.ts

Findings kept as literal (2/10): plans.test.ts and recommendations-enabled-providers.test.ts use the UUID inside jest.mock() factory callbacks. Jest hoists these factories before imports, and jest.requireActual('../permissions') also fails here because permissions.ts has a top-level import * as state from './state' which creates a circular initialization with the mocked state module. The literal is retained with an inline comment, and permissions.test.ts already has a pinning test that guards the UUID value.

All 65 test suites pass (2196 passed, 1 skipped). Build clean.

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

@cristim: Thanks for the thorough follow-up! The 8/10 fixes look great. The rationale for the 2 retained literals in plans.test.ts and recommendations-enabled-providers.test.ts is sound — Jest's hoisting of jest.mock() factories before module imports is a well-known constraint, and the circular initialization issue with permissions.ts → state makes jest.requireActual unworkable there. The inline comments + the UUID pinning test in permissions.test.ts provide a solid safety net.

Triggering the review now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim
Copy link
Copy Markdown
Member Author

cristim commented Jun 1, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/all-users Affects every user priority/p2 Backlog-worthy severity/high Significant harm triaged Item has been triaged type/security Security finding urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant