From 96e9472707aca002e65bb636d2f28b8347da5fd0 Mon Sep 17 00:00:00 2001 From: Jesus Velarde Date: Sat, 14 Nov 2026 16:12:24 -0800 Subject: [PATCH] test: move the create-user soft budget test to the integration tier and type the email test --- .../CreateUserButton.integration.test.tsx | 18 +++++++++++ .../src/components/CreateUserButton.test.tsx | 32 +------------------ .../send_emails/test_base_email.py | 13 +++++--- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/litellm/ui/litellm-dashboard/src/components/CreateUserButton.integration.test.tsx b/litellm/ui/litellm-dashboard/src/components/CreateUserButton.integration.test.tsx index e89211596f3..1696e21b2d7 100644 --- a/litellm/ui/litellm-dashboard/src/components/CreateUserButton.integration.test.tsx +++ b/litellm/ui/litellm-dashboard/src/components/CreateUserButton.integration.test.tsx @@ -109,3 +109,21 @@ describe("custom User ID invitation", () => { expect(screen.getByRole("textbox", { name: "User ID", exact: true })).toHaveValue(""); }); }); + +describe("soft budget", () => { + it.each([ + ["25.5", { soft_budget: 25.5 }], + ["", {}], + ])("sends Soft Budget %j as %j on /user/new", async (typed, expected) => { + const user = userEvent.setup(); + renderForm(false); + await user.click(screen.getByRole("button", { name: "+ Invite User" })); + fireEvent.change(screen.getByRole("textbox", { name: "User Email" }), { target: { value: "soft@example.test" } }); + fireEvent.change(screen.getByRole("spinbutton", { name: /Soft Budget/ }), { target: { value: typed } }); + await user.click(screen.getByRole("button", { name: "Invite User", exact: true })); + await waitFor(() => expect(createUser).toHaveBeenCalledTimes(1)); + const body = createUser.mock.calls[0][0]; + expect(body).toMatchObject({ user_email: "soft@example.test", ...expected }); + expect("soft_budget" in body).toBe("soft_budget" in expected); + }); +}); diff --git a/litellm/ui/litellm-dashboard/src/components/CreateUserButton.test.tsx b/litellm/ui/litellm-dashboard/src/components/CreateUserButton.test.tsx index 5a49da58f0c..d04272cecd3 100644 --- a/litellm/ui/litellm-dashboard/src/components/CreateUserButton.test.tsx +++ b/litellm/ui/litellm-dashboard/src/components/CreateUserButton.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"; +import { render, screen, waitFor, within } from "@testing-library/react"; import userEvent, { PointerEventsCheckLevel } from "@testing-library/user-event"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { describe, it, expect, vi, beforeEach } from "vitest"; @@ -311,36 +311,6 @@ describe("CreateUserButton", () => { expectControlBesideLabel(within(dialog).getByRole("checkbox", { name: "Send invitation email" })); }); - describe("soft budget", () => { - it.each([ - ["25.5", { soft_budget: 25.5 }], - ["", {}], - ])("sends soft_budget %j as %j in the create payload", async (typed, expected) => { - const user = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never }); - mockUserCreateCall.mockResolvedValue({ data: { user_id: "soft-user" } }); - mockInvitationCreateCall.mockResolvedValue({ - id: "inv-soft", - user_id: "soft-user", - has_user_setup_sso: false, - } as any); - - renderWithProviders( - , - ); - await user.click(await screen.findByRole("button", { name: /\+ invite user/i })); - - const dialog = screen.getByRole("dialog", { name: /invite user/i }); - fireEvent.change(within(dialog).getByLabelText(/user email/i), { target: { value: "soft@example.com" } }); - fireEvent.change(within(dialog).getByLabelText(/soft budget/i), { target: { value: typed } }); - await user.click(within(dialog).getByRole("button", { name: /invite user/i })); - - await waitFor(() => expect(mockUserCreateCall).toHaveBeenCalledTimes(1)); - const payload = mockUserCreateCall.mock.calls[0][2]; - expect(payload).toMatchObject({ user_email: "soft@example.com", ...expected }); - expect("soft_budget" in payload).toBe("soft_budget" in expected); - }); - }); - describe("organizations", () => { it("should send organizations list in POST body when organizations are selected", async () => { const { useOrganizations } = await import("@/app/(dashboard)/hooks/organizations/useOrganizations"); diff --git a/tests/unit/enterprise/enterprise_callbacks/send_emails/test_base_email.py b/tests/unit/enterprise/enterprise_callbacks/send_emails/test_base_email.py index 68242cb1fcf..9b4fade9526 100644 --- a/tests/unit/enterprise/enterprise_callbacks/send_emails/test_base_email.py +++ b/tests/unit/enterprise/enterprise_callbacks/send_emails/test_base_email.py @@ -1539,9 +1539,14 @@ async def test_expiry_email_contains_alias_utc_expiry_and_dashboard_link_without ], ) async def test_soft_budget_alert_email_names_what_crossed_the_budget( - base_email_logger, mock_send_email, mock_lookup_user_email, event_group, owner_line, other_line -): - event = WebhookEvent( + base_email_logger: BaseEmailLogger, + mock_send_email: mock.MagicMock, + mock_lookup_user_email: mock.MagicMock, + event_group: Litellm_EntityType, + owner_line: str, + other_line: str, +) -> None: + event: Final = WebhookEvent( user_id="test_user", user_email="test@example.com", token="hashed-token", @@ -1555,6 +1560,6 @@ async def test_soft_budget_alert_email_names_what_crossed_the_budget( with mock.patch.dict(os.environ, {"PROXY_BASE_URL": "http://test.com"}): await base_email_logger.send_soft_budget_alert_email(event) - html_body = mock_send_email.call_args[1]["html_body"] + html_body: Final[str] = mock_send_email.call_args[1]["html_body"] assert owner_line in html_body assert other_line not in html_body