mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-13 08:52:02 +00:00
fix(ui): align customer member budget form with API contract (#51523)
This commit is contained in:
parent
78db4f1662
commit
f85aa27c16
3 changed files with 50 additions and 6 deletions
|
|
@ -35,7 +35,7 @@ const member: Member = {
|
|||
customer_member_id: "member-1",
|
||||
parent_team_id: "team-1",
|
||||
alias: "Member One",
|
||||
budget_template_id: "template-1",
|
||||
budget_id: "template-1",
|
||||
budget_override: { max_budget: 20 },
|
||||
effective_budget: { max_budget: 20 },
|
||||
budget_override_expires_at: "2026-12-01T12:00:00Z",
|
||||
|
|
@ -116,13 +116,31 @@ describe("CustomerHierarchySettings", () => {
|
|||
expect(template).toHaveTextContent("Pro");
|
||||
await user.click(screen.getByRole("button", { name: "Save changes" }));
|
||||
await waitFor(() => expect(onSaved).toHaveBeenCalledOnce());
|
||||
expect(await lastRequest().clone().json()).toEqual({ budget_template_id: "template-2" });
|
||||
expect(new URL(lastRequest().url).pathname).toBe("/management/v1/customer_members/member-1");
|
||||
expect(await lastRequest().clone().json()).toEqual({ budget_id: "template-2" });
|
||||
});
|
||||
|
||||
it("should clear the template without changing the existing override", async () => {
|
||||
const user = userEvent.setup();
|
||||
fetchMock.mockResolvedValue(json({ ...member, budget_id: null }));
|
||||
render(
|
||||
<CustomerHierarchySettings
|
||||
entity={{ kind: "member", record: member }}
|
||||
organization={organization}
|
||||
onSaved={onSaved}
|
||||
onDeleted={onDeleted}
|
||||
/>,
|
||||
);
|
||||
await chooseSelectOption(user, screen.getByRole("combobox", { name: "Budget template" }), "No template");
|
||||
await user.click(screen.getByRole("button", { name: "Save changes" }));
|
||||
await waitFor(() => expect(onSaved).toHaveBeenCalledOnce());
|
||||
expect(await lastRequest().clone().json()).toEqual({ budget_id: null });
|
||||
});
|
||||
|
||||
it("should identify an unavailable stored template instead of displaying No template", () => {
|
||||
render(
|
||||
<CustomerHierarchySettings
|
||||
entity={{ kind: "member", record: { ...member, budget_template_id: "retired-template" } }}
|
||||
entity={{ kind: "member", record: { ...member, budget_id: "retired-template" } }}
|
||||
organization={organization}
|
||||
onSaved={onSaved}
|
||||
onDeleted={onDeleted}
|
||||
|
|
@ -33,13 +33,39 @@ const member: Member = {
|
|||
customer_member_id: "member-1",
|
||||
parent_team_id: "team-1",
|
||||
alias: "Member One",
|
||||
budget_template_id: "template-1",
|
||||
budget_id: "template-1",
|
||||
budget_override: { max_budget: 20, rpm_limit: 4 },
|
||||
effective_budget: { max_budget: 20, rpm_limit: 4 },
|
||||
budget_override_expires_at: "2026-12-01T12:00:00Z",
|
||||
};
|
||||
|
||||
describe("customer hierarchy settings payloads", () => {
|
||||
it("should load the member template from the API response", () => {
|
||||
expect(customerHierarchyFields({ kind: "member", record: member }).templateId).toBe(member.budget_id);
|
||||
});
|
||||
|
||||
it("should allow an alias-only edit for a member using a template without an override", () => {
|
||||
const entity: CustomerHierarchyEntity = {
|
||||
kind: "member",
|
||||
record: { ...member, budget_override: null, budget_override_expires_at: null },
|
||||
};
|
||||
expect(buildCustomerHierarchyPatch(entity, { ...customerHierarchyFields(entity), alias: "Updated" })).toEqual({
|
||||
ok: true,
|
||||
patch: { kind: "member", body: { alias: "Updated" } },
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["template-2", "template-2"],
|
||||
["", null],
|
||||
])("should submit template selection %s without changing the override", (templateId, budgetId) => {
|
||||
const entity: CustomerHierarchyEntity = { kind: "member", record: member };
|
||||
expect(buildCustomerHierarchyPatch(entity, { ...customerHierarchyFields(entity), templateId })).toEqual({
|
||||
ok: true,
|
||||
patch: { kind: "member", body: { budget_id: budgetId } },
|
||||
});
|
||||
});
|
||||
|
||||
it("should leave untouched settings out of the patch", () => {
|
||||
const entity: CustomerHierarchyEntity = { kind: "organization", record: organization };
|
||||
expect(buildCustomerHierarchyPatch(entity, customerHierarchyFields(entity))).toEqual({
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export const customerHierarchyFields = (entity: CustomerHierarchyEntity): Custom
|
|||
tpm: asText(budget?.tpm_limit),
|
||||
rpm: asText(budget?.rpm_limit),
|
||||
allowedModels: budget?.allowed_models?.join("\n") ?? "",
|
||||
templateId: entity.kind === "member" ? entity.record.budget_template_id ?? "" : "",
|
||||
templateId: entity.kind === "member" ? entity.record.budget_id ?? "" : "",
|
||||
overrideExpiry: entity.kind === "member" ? localDateTime(entity.record.budget_override_expires_at) : "",
|
||||
};
|
||||
};
|
||||
|
|
@ -125,7 +125,7 @@ const memberPatch = (
|
|||
const initial = customerHierarchyFields({ kind: "member", record: member });
|
||||
if (!fields.templateId && !fields.budgetEnabled) return { ok: false, error: "budgetRequired" };
|
||||
const body: components["schemas"]["MemberPatch"] = aliasChanges(member.alias, fields.alias);
|
||||
if (fields.templateId !== initial.templateId) body.budget_template_id = fields.templateId || null;
|
||||
if (fields.templateId !== initial.templateId) body.budget_id = fields.templateId || null;
|
||||
if (changed) body.budget_override = budget;
|
||||
if (!fields.budgetEnabled && initial.budgetEnabled) body.budget_override_expires_at = null;
|
||||
if (fields.budgetEnabled && fields.overrideExpiry !== initial.overrideExpiry) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue