Repository navigation
🧪 fix: Serve Roles, Groups, Grants and the Audit Log from the E2E Mock and Update the Access and Grants Specs - #202
Conversation
…kend Since #185 the /_app route loads GET /api/admin/grants/effective before the panel renders, and fails closed when it can't. e2e/mock-backend.mjs never served that route, so every signed-in page in the mock e2e run showed "Could not verify permissions". The mock now answers it for its admin tokens with every capability in SystemCapabilities, refusing other requests the way it refuses /api/admin/verify; both share one token check.
…k and Update the Access and Grants Specs e2e/mock-backend.mjs served none of the admin API routes the Access and Grants pages read, so most of access.spec.ts and grants.spec.ts failed against it. The mock now holds in-memory fixtures (users, the ADMIN and USER roles plus Auditor, Billing and Support, the Engineering and Marketing groups, role grants and a hash-chained grant history) and answers the roles, groups, user search, grants and audit-log routes in LibreChat's response shapes. Group search is LibreChat's case-insensitive substring match on name, email and description; user search matches word prefixes. Writes change the fixtures, so create, edit and delete flows work. Path parameters are matched per route and every admin route shares the existing token check. The specs also asserted UI the panel does not have: title-cased role names, a searchbox role, kebab menus, keyboard reordering, a roles/groups type filter and user grants. They now follow the current panel, wait for filtered results instead of sleeping, and delete the roles and groups they create. Under E2E_LIVE=1 the Groups-tab tests create the groups they list and delete them afterwards, and the tests that read users or grant history only the mock has are skipped.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 39ee16a958
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… CSV Export From the Mock and Test That a Role Rename Leaves Its Grants Behind
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ecb944b42d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…he Mock and Find Staged Groups Past the First Page
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6727de9ca8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Codex Review: Didn't find any major issues. Bravo. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Pull Request Template
Summary
Stacks on #190, which adds
GET /api/admin/grants/effectiveto the mock. Merge #190 first.e2e/mock-backend.mjsserved none of the admin API routes that the Access and Grants pages read, so most ofaccess.spec.tsandgrants.spec.tsfailed against it. The specs were also written before the server functions were wired to LibreChat's admin API, and several of them assert UI that the panel doesn't have. This PR changes only the mock and the e2e specs, not the app.Mock backend
packages/api/src/adminhandlers and the admin route files:buildGroupQuery.nextCursor; and the CSV export. The export follows LibreChat'sexportAuditLogCsvHandler: a UTF-8 BOM, the same 17 columns and cell escaping, CRLF line ends, newest first, the list's filters, and the sameContent-Type,Content-DispositionandCache-Controlheaders. A single entry by id (GET /api/admin/audit-log/:id) answers like LibreChat'sgetAuditLogEntryHandler:{ entry }, 400Invalid idfor a malformed id and 404Not foundfor an unknown one. The panel asks for it when a permalink opens the drawer.grant.removedentry for each grant.renameRole(packages/api/src/admin/roles.ts) only callsupdateUsersByRoleandupdateRoleByName, andupdateRoleByName(packages/data-schemas/src/methods/role.ts) only$sets the role document. Grants, ACL entries and config profiles stay keyed by the old name, so a role created later under that name gets them back. A test checks this (see below).Invalid principal type, because LibreChat grants capabilities only to roles.METHOD /pathwith:paramsegments and matched in the order they are declared. Every admin route uses the existing token check.GET /api/admin/audit-log/:id: no spec deep-links an entry.GET /api/admin/config/meta: the app shell requests it on every page and treats the 404 as an older LibreChat.Spec changes. Each change follows what the panel does now. The kebab menus, reordering, type filter and user-grant button were already absent in this repo's first commit.
AdminandBillingADMINandUSER.ADMIN. The tab and role-search tests now useUSERinstead ofBilling, so they also run against a live LibreChat.getByRole('searchbox')SearchFieldrenders a plain text input.getByRole('textbox', { name: /^search/i })getByText(name)after creating a group or roleexact: true[data-item-id]).badge-roleon each grants rowexact: trueNew tests:
access.spec.ts) creates a role, grants it Read usage on the Grants page, renames it and checks that the renamed role shows "No capabilities". It then renames the role back, checks that the grant shows again, and deletes the role. It runs against the mock only (see Live runs). Its teardown renames the role back to its original name, whatever name it has when the test stops, and then deletes it, so the grant goes with it.grants.spec.ts) creates a role, grants it Read usage, searches the audit log for the role's name and exports. The CSV must hold exactly thatgrant.assignedentry, with the role as the target andread:usageas the capability. It runs against the mock only (see Live runs).audit-log-YYYY-MM-DD.csv), the BOM, the header row and the rows. They also check theContent-TypeandContent-Dispositionof the panel's export response, whichexportAuditLogServerFnsets itself, so those two assertions cover the panel rather than the mock's headers.e2e/access.ts, which both specs import.Race fixes in
grants.spec.ts:Live runs (
E2E_LIVE=1).role.created,role.updated,role.deleted,group.created,group.deleted,grant.assigned,grant.removedand so on) and for every audit-log export (audit.exported). Its admin API can't delete audit entries. So the 5 tests whose purpose is such a write run against the mock only, and skip live withAUDITED_WRITES_SKIP_REASON, which this PR adds toe2e/live.ts:MOCK_FIXTURES_SKIP_REASON, which this PR also adds toe2e/live.ts. A fresh LibreChat seeds only ADMIN and USER. Its admin API also can't stage the members or the history without leaving data behind, because it can't create users and it never deletes audit entries. The six Audit Log tests that read the seeded history sit in aseeded historydescribe that skips live. This way the tab'sbeforeEach, which opens the log, doesn't run for them, and they don't writeaudit.readentries.groupsfixture. Against the mock it names Engineering and Marketing. Live, it creates both groups, in a page of its own so the creation toasts never reach a test's page, and deletes them when the worker ends. Live,access.spec.tsruns in order in one worker, so the groups are staged once per run, and again only after a failed test, which starts a new worker. Each staging writes 2group.createdentries and 4group.deletedentries, because LibreChat records each group delete as pending and then as success.audit.readentry: 7 per run, from the six live tests that open the Audit Log tab. These entries name no role or group.finally. That still runs when staging fails part-way, for example when the second group can't be created.finallywould run against a closed context, and the groups already created would stay behind. So each staging and cleanup action and page load fails after 10 s, and the fixture has its own 60 s timeout. A stalled step throws while the browser is still open. Thefinallythen closes the staging context and deletes the groups from a fresh context with the same sign-in, so cleanup works whatever state staging left its page in.Engineering 3f0c…). Names built fromDate.now()alone could repeat across parallel workers: LibreChat refuses a second role with the same name, and two groups with the same name have two identical Delete buttons, which fails Playwright's strict mode.deleteFromListsearches for each name and deletes only the rows the tab lists. It waits until the row is gone, and it tries every name even if an earlier delete fails. A test sets the name it will delete before it submits the create dialog, so a create that succeeds and then times out is still cleaned up. On 🧪 fix: Serve the Admin's Effective Capabilities from the E2E Mock Backend #190 the create tests never cleaned up, and its live run left one group and two roles behind.Known limits of the live group staging. Live runs can still leave a UUID-named test group behind in two cases. No injected stall of a Playwright step leaks one any more.
Each would need cleanup to wait for in-flight creates and retry until the budget runs out. That only matters on a slow live test backend, so it is left for later.
Not in this PR. Three axe tests in
grants.spec.tsstill fail, against the mock and against a live LibreChat alike. They also failed in the live run on #190, whose panel code is the same asmain's. The cause is in the panel, not in the fixtures:color-contrast): the success badge (Active in the grants table, Granted in the audit log) uses click-ui's--click-badge-opaque-color-text-success, #008a0b on #ebffec. That is 4.32:1 at 10px, and AA needs 4.5:1.nested-interactive): each date filter is click-ui'sDatePicker. Its trigger button contains the readonly input, andtabIndex = -1still leaves the input focusable.nested-interactive): each category header inCapabilityPanelis adiv role="button"that contains its "Select all" switch.A fourth issue shows up only when a scan runs while a toast is open, so no test here fails on it. click-ui's toast viewport is an
olwhose toasts areli role="status"(axelist), and Radix's focus-proxy spans arearia-hiddenbut havetabindex="0"(aria-hidden-focus). A live Groups-tab scan hit it while the staging toasts were still open, which is why thegroupsfixture stages its groups in a page of its own. All four issues need a panel a11y fix and are outside this fixtures PR.Change Type
Testing
access+grants)access+grants, mockaccess+grants, mock,--workers=4 --repeat-each=3access+grants, live, fresh databaseaccess+grants, live,--workers=4access+grants, live, fresh database,--workers=4 --repeat-each=2access.spec.ts, live, with 60 extra groups that sort first,--workers=4 --repeat-each=2access.spec.ts, live,--workers=4access.spec.ts, live,--workers=4 --repeat-each=2login.spec.tsstill passes in full.--workers=4 --repeat-each=2run added 26 entries: 14audit.read, 4group.createdand 8group.deleted.--workers=4run added 13: 7audit.read, 2group.createdand 4group.deleted.role.*,grant.*oraudit.exportedentry.--workers=4 --repeat-each=2), added 132 entries: 30audit.read, 4audit.exported, 4grant.assigned, 4grant.removed, 18group.created, 36group.deleted, 8role.created, 16role.deleted, 8role.permissions_updatedand 4role.updated.groupsfixture throw after creating its first group, and in a second run after creating both. In both live runs, every group the fixture created was deleted. The previous fixture, given the same failure after the first group, left 4 groups behind. The run that throws after both groups was repeated on the final fixture, and it deleted all 8 groups it created.Target page, context or browser has been closedorTest ended, and the two runs left 4 and 8 groups behind.AAA Seed Group 01–60sort before the staged groups, so the first page doesn't show the staged groups. With those groups added, the previous Groups-tab tests failed all four, and this branch'saccess.spec.tspassed in full. The 60 groups were removed afterwards.ineer,productorMARKETfinds Engineering, Engineering and Marketing. A user search forali chefinds Alice Chen. A grant read or write for auserorgroupprincipal gets{"error":"Invalid principal type"}.npx tsc --noEmit, which also type-checkse2e/,npx eslint src/ --max-warnings 0, prettier on the changed files,bun run test:lint-rules,npx vitest run(154 files, 3338 tests) andbun run buildall pass. ESLint's config covers onlysrc/, so thee2e/files are type-checked and formatted but not linted.Test Configuration:
E2E_ADMIN_EMAIL=admin@test.com E2E_ADMIN_PASSWORD=password npx playwright test e2e/access.spec.ts e2e/grants.spec.ts --workers=2. This startsvite devande2e/mock-backend.mjs.E2E_LIVE=1 E2E_BASE_URL=<panel> E2E_ADMIN_EMAIL=<admin> E2E_ADMIN_PASSWORD=<password> npx playwright test e2e/access.spec.ts e2e/grants.spec.ts --workers=2, against a production build of the panel and a LibreChatdevbackend on a fresh database.Checklist