fix(ui): prefer form values over API echo in regenerate update payload

The regenerate endpoint returns a GenerateKeyResponse that inherits
max_budget/tpm_limit/rpm_limit from KeyRequestBase, so the API echoes
the existing values back. The previous updatedKeyData layout spread
...response *after* the explicit formValues assignments, which meant
the user's just-submitted edits were silently overwritten by the API
echo before being propagated to the parent via onKeyUpdate.

Reorder so the response spread comes first and the formValues-derived
fields override it, and add a regression test that mocks a response
with stale limits to lock the behavior in. Also drop the two leftover
debug console.log statements.
This commit is contained in:
Yuneng Jiang
2026-04-09 20:25:10 -07:00
parent 839d9bd5f3
commit f95ef935ef
2 changed files with 35 additions and 10 deletions
@@ -219,6 +219,34 @@ describe("RegenerateKeyModal", () => {
expect(updateCall.key_name).toBe("sk-new-regenerated-key");
});
it("should pass form values to onKeyUpdate even when the API echoes back different limits", async () => {
// Regression: when the regenerate endpoint returns GenerateKeyResponse, it echoes
// back the existing max_budget / tpm_limit / rpm_limit. The modal must prefer the
// values the user just submitted, not whatever the server echoes.
const user = userEvent.setup();
mockRegenerateKeyCall.mockResolvedValue({
key: "sk-new-regenerated-key",
token: "new-token-hash",
// stale values echoed from the server
max_budget: 9999,
tpm_limit: 9999,
rpm_limit: 9999,
});
renderWithProviders(<RegenerateKeyModal {...defaultProps} />);
await user.click(screen.getByRole("button", { name: /Regenerate/ }));
await waitFor(() => {
expect(mockOnKeyUpdate).toHaveBeenCalledOnce();
});
const updateCall = mockOnKeyUpdate.mock.calls[0][0];
// The form's pre-filled values (from makeToken) must win over the API echo.
expect(updateCall.max_budget).toBe(100);
expect(updateCall.tpm_limit).toBe(5000);
expect(updateCall.rpm_limit).toBe(500);
});
it("should display key alias in success view", async () => {
const user = userEvent.setup();
mockRegenerateKeyCall.mockResolvedValue({
@@ -111,23 +111,20 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
setRegeneratedKey(response.key);
NotificationManager.success("Virtual Key regenerated successfully");
console.log("Full regenerate response:", response); // Debug log to see what's returned
// Create updated key data with ALL new values from the response
// Build the update payload. Spread the API response first so any new
// fields it returns (new token, timestamps, etc.) are captured, then
// override with the explicit form values the user's just-submitted
// edits must win over whatever the API echoes back.
const updatedKeyData: Partial<KeyResponse> = {
// Use the new token/key ID from the response (this is what was missing!)
token: response.token || response.key_id || selectedToken.token, // Try different possible field names
key_name: response.key, // This is the new secret key string
...response,
token: response.token || response.key_id || selectedToken.token,
key_name: response.key,
max_budget: formValues.max_budget,
tpm_limit: formValues.tpm_limit,
rpm_limit: formValues.rpm_limit,
expires: formValues.duration ? calculateNewExpiryTime(formValues.duration) : selectedToken.expires,
// Include any other fields that might be returned by the API
...response, // Spread the entire response to capture all updated fields
};
console.log("Updated key data with new token:", updatedKeyData); // Debug log
// Update the parent component with new key data
if (onKeyUpdate) {
onKeyUpdate(updatedKeyData);