fix(FR-2978): send empty JSON body in vfolder leave_invited to avoid 400#7609
Conversation
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has required the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
Coverage Report for root-coverage
File CoverageNo changed files found. |
Coverage Report for react-coverage (./react)
File Coverage
|
||||||||||||||||||||||||||||||||||||||
f565c6a to
3a3521c
Compare
|
Thanks for the catch — the workspace package
PR description updated to reflect the new scope (3 files, both client copies + the TypeScript signature). |
There was a problem hiding this comment.
Pull request overview
Fixes a 400 error when leaving a shared (invited) virtual folder by ensuring the POST /folders/{name}/leave request always sends a valid JSON body, aligning with the manager’s BodyParam[LeaveVFolderReq] parsing requirement.
Changes:
- Update
VFolder.leave_invited()(browser client + legacy node client) to send{}instead ofnull, and optionally include{ shared_user_uuid }when provided. - Extend the React-side
BackendAIClient.vfolder.leave_invitedTypeScript signature to accept the new optionalsharedUserUuidargument.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/lib/backend.ai-client-node.ts | Sends {} (or { shared_user_uuid }) in leave_invited() to avoid invalid empty-string JSON bodies in the legacy node client. |
| react/src/hooks/index.tsx | Updates the BackendAIClient type to include the optional sharedUserUuid parameter for leave_invited(). |
| packages/backend.ai-client/src/resources/vfolder.ts | Sends {} (or { shared_user_uuid }) in leave_invited() to avoid 400 responses in the browser client. |
e47e4d6 to
3a334b0
Compare
3a334b0 to
8408d6f
Compare
8408d6f to
4f59d1b
Compare
ed46ce5 to
4e8dcc7
Compare
4e8dcc7 to
04ccea6
Compare
4f59d1b to
3c01ab8
Compare
3c01ab8 to
f656bc4
Compare
04ccea6 to
7f76036
Compare
Merge activity
|
…400 (#7609) **Jira**: [FR-2978](https://lablup.atlassian.net/browse/FR-2978) > Targets `main` directly. The previously-stacked generalized fix (#7651, which made `newSignedRequest` send `{}` for *every* null-body non-GET request) was dropped: on legacy backends `@check_api_params` only falls back to `dict(request.query)` when the body is empty, so a blanket `{}` body would suppress that fallback and silently drop query-string params on `destroy`/`restart` (e.g. `forced`, `owner_access_key`). This PR keeps the fix targeted to `leave_invited`, the one endpoint whose handler (`BodyParam[LeaveVFolderReq]`) actually rejects a null body. It also extends the client signature with the optional `sharedUserUuid` parameter (admin-on-behalf-of). ## Summary Fixes a 400 error when a user tries to leave a shared folder via the **Shared Folder Permission** modal (both V1 `SharedFolderPermissionInfoModal` and V2 `SharedFolderPermissionInfoModalV2`). ## Root cause The Backend.AI manager's `POST /folders/{name}/leave` REST route parses its body via `BodyParam[LeaveVFolderReq]`. All fields on `LeaveVFolderReq` are optional, but the body itself must still be valid JSON. The frontend client's `VFolder.leave_invited` was passing `null` as the body. `newSignedRequest` serializes `null` to an empty string `''` and sends it with `Content-Type: application/json`. An empty string is not valid JSON, so the manager rejects the request with 400 before the handler runs. Both `SharedFolderPermissionInfoModal.tsx` (V1) and `SharedFolderPermissionInfoModalV2.tsx` (V2) call the same `leave_invited` client method, so the bug reproduces in both flows. ## Change The fix is applied in **both** copies of the API client to keep them consistent: - `packages/backend.ai-client/src/resources/vfolder.ts` — **load-bearing for the React UI**. `react/src/global-stores.ts` registers `globalThis.BackendAIClient = ai.backend.Client` from this workspace package. This is what runs in the browser. - `src/lib/backend.ai-client-node.ts` — legacy ESM client; only consumed by `src/wsproxy/` (Electron desktop's websocket proxy). Kept in sync to avoid a divergent regression if the Electron side ever calls the leave flow. - `react/src/hooks/index.tsx` — extends the `BackendAIClient.vfolder.leave_invited` TypeScript signature with the new optional `sharedUserUuid` parameter (matches `LeaveVFolderReq.shared_user_uuid` for future admin-on-behalf-of flows). Both methods now send `{}` as the JSON body by default. When `sharedUserUuid` is provided, they send `{ shared_user_uuid: sharedUserUuid }`. No existing call site needs to change for the immediate fix — both modals continue calling `leave_invited(folderId)` with a single argument. ## Test plan - [ ] As a regular user with an accepted folder invitation, open the data page → click on the shared folder → permission modal → leave button → confirm. Expect a success toast and the folder to disappear from the list (no 400). - [ ] Repeat for both V1 modal (regular page) and V2 modal (project admin data page). - [ ] CI: lint / format / relay drift / TypeScript pass. ## Verification `bash scripts/verify.sh` was run locally. Relay, Lint, Format pass. The TypeScript step reports pre-existing environmental errors (duplicate `@types/react` between `react/` and `packages/backend.ai-ui/`) that affect files unrelated to this change — CI's clean install is authoritative. [FR-2978]: https://lablup.atlassian.net/browse/FR-2978?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
f656bc4 to
c590f01
Compare
Resolves #7611 (FR-2983) Stacked on #7609 (FR-2978) — the leave action this test exercises only succeeds with the client fix in that PR; merge bottom-up. ## Summary Adds an E2E regression test that drives the full share → accept → leave flow against a real Backend.AI cluster. The test reproduces FR-2978 (`POST /folders/{name}/leave` returning 400 because the client sent `null` instead of valid JSON) end-to-end, so the manager↔frontend contract is exercised at the request body level — not just at the TypeScript level — going forward. ## Changes - `e2e/utils/test-util.ts` — new `leaveSharedFolderAndVerify(page, folderName)` helper. Opens the `SharedFolderPermissionInfoModal` for an invited folder, clicks the Leave button (located by the Tooltip's accessible name *"Leave the shared folder"*), confirms the `Popconfirm`, and asserts both the success toast and the folder's disappearance from the user's Active list. - `e2e/vfolder/vfolder-crud.spec.ts` — new test *"Invitee can leave a shared vFolder"* extending the existing `VFolder Sharing` block. Reuses `shareVFolderAndVerify` / `acceptAllInvitationAndVerifySpecificFolder`, then drives the new leave helper. - `e2e/E2E_COVERAGE_REPORT.md` — registers the new flow under VFolder/Data and bumps the route + total counts. ## Local validation Tried to run the test against the worktree's own Vite dev server, but the host i18n bundle on the dev server in this worktree consistently returns raw keys (`login.E-mailOrUsername` instead of the translated label) — both my new test *and* the existing `User can share vFolder` smoke test fail at the `getByLabel('Email or Username')` step for the same reason. Sibling worktrees' dev servers translate correctly, so the issue is local to this worktree's environment (post FR-2925 TypeScript v6 upgrade), not the test code. CI / the dedicated e2e watchdog runs in a clean environment without this regression. Once #7609 is merged and the e2e watchdog picks this up, the test should pass; if it doesn't, I will iterate on selectors based on the CI failure report. ## Test plan - [ ] CI e2e watchdog runs the new test against a real cluster and reports PASS. - [ ] If FR-2978 is reverted (or the client regresses to sending `null` body), this test fails — confirming it is a real regression guard.

Jira: FR-2978
Summary
Fixes a 400 error when a user tries to leave a shared folder via the Shared Folder Permission modal (both V1
SharedFolderPermissionInfoModaland V2SharedFolderPermissionInfoModalV2).Root cause
The Backend.AI manager's
POST /folders/{name}/leaveREST route parses its body viaBodyParam[LeaveVFolderReq]. All fields onLeaveVFolderReqare optional, but the body itself must still be valid JSON.The frontend client's
VFolder.leave_invitedwas passingnullas the body.newSignedRequestserializesnullto an empty string''and sends it withContent-Type: application/json. An empty string is not valid JSON, so the manager rejects the request with 400 before the handler runs.Both
SharedFolderPermissionInfoModal.tsx(V1) andSharedFolderPermissionInfoModalV2.tsx(V2) call the sameleave_invitedclient method, so the bug reproduces in both flows.Change
The fix is applied in both copies of the API client to keep them consistent:
packages/backend.ai-client/src/resources/vfolder.ts— load-bearing for the React UI.react/src/global-stores.tsregistersglobalThis.BackendAIClient = ai.backend.Clientfrom this workspace package. This is what runs in the browser.src/lib/backend.ai-client-node.ts— legacy ESM client; only consumed bysrc/wsproxy/(Electron desktop's websocket proxy). Kept in sync to avoid a divergent regression if the Electron side ever calls the leave flow.react/src/hooks/index.tsx— extends theBackendAIClient.vfolder.leave_invitedTypeScript signature with the new optionalsharedUserUuidparameter (matchesLeaveVFolderReq.shared_user_uuidfor future admin-on-behalf-of flows).Both methods now send
{}as the JSON body by default. WhensharedUserUuidis provided, they send{ shared_user_uuid: sharedUserUuid }. No existing call site needs to change for the immediate fix — both modals continue callingleave_invited(folderId)with a single argument.Test plan
Verification
bash scripts/verify.shwas run locally. Relay, Lint, Format pass. The TypeScript step reports pre-existing environmental errors (duplicate@types/reactbetweenreact/andpackages/backend.ai-ui/) that affect files unrelated to this change — CI's clean install is authoritative.