mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
fix(ui): preserve subpath in bulk invite links
This commit is contained in:
parent
cee6c9c724
commit
a393ec65e7
2 changed files with 76 additions and 5 deletions
|
|
@ -1,6 +1,17 @@
|
|||
import { render } from "@testing-library/react";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { beforeEach, describe, it, expect, vi } from "vitest";
|
||||
import Papa from "papaparse";
|
||||
import type { ParseConfig, ParseResult } from "papaparse";
|
||||
import BulkCreateUsersButton from "./bulk_create_users_button";
|
||||
import { getProxyUISettings, invitationCreateCall, userCreateCall } from "./networking";
|
||||
|
||||
vi.mock("papaparse", () => ({
|
||||
default: {
|
||||
parse: vi.fn(),
|
||||
unparse: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("./networking", () => ({
|
||||
userCreateCall: vi.fn(),
|
||||
|
|
@ -21,8 +32,64 @@ vi.mock("./molecules/notifications_manager", () => ({
|
|||
}));
|
||||
|
||||
describe("BulkCreateUsersButton", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
window.history.pushState({}, "", "/");
|
||||
|
||||
vi.mocked(getProxyUISettings).mockResolvedValue({
|
||||
PROXY_BASE_URL: null,
|
||||
PROXY_LOGOUT_URL: null,
|
||||
DEFAULT_TEAM_DISABLED: false,
|
||||
SSO_ENABLED: false,
|
||||
});
|
||||
vi.mocked(userCreateCall).mockResolvedValue({
|
||||
user_id: "user-1",
|
||||
key: "key-1",
|
||||
});
|
||||
vi.mocked(invitationCreateCall).mockResolvedValue({
|
||||
id: "invite-1",
|
||||
});
|
||||
vi.mocked(Papa.parse).mockImplementation((_file: unknown, config?: ParseConfig<string[]>) => {
|
||||
const parsedCsv = {
|
||||
data: [
|
||||
["user_email", "user_role"],
|
||||
["person@example.com", "internal_user"],
|
||||
],
|
||||
errors: [],
|
||||
meta: {},
|
||||
} as ParseResult<string[]>;
|
||||
|
||||
config?.complete?.(parsedCsv);
|
||||
return undefined as ReturnType<typeof Papa.parse>;
|
||||
});
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
const { getByText } = render(<BulkCreateUsersButton accessToken="test-token" teams={[]} possibleUIRoles={null} />);
|
||||
expect(getByText("+ Bulk Invite Users")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps the dashboard subpath in generated invitation links", async () => {
|
||||
window.history.pushState({}, "", "/litellm/ui");
|
||||
const user = userEvent.setup();
|
||||
render(<BulkCreateUsersButton accessToken="test-token" teams={[]} possibleUIRoles={null} />);
|
||||
|
||||
await user.click(screen.getByText("+ Bulk Invite Users"));
|
||||
|
||||
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||
expect(input).not.toBeNull();
|
||||
|
||||
const csv = new File(["user_email,user_role\nperson@example.com,internal_user\n"], "users.csv", {
|
||||
type: "text/csv",
|
||||
});
|
||||
await user.upload(input, csv);
|
||||
|
||||
await screen.findByText("person@example.com");
|
||||
await user.click(screen.getAllByRole("button", { name: /Create 1 Users/i })[0]);
|
||||
|
||||
await waitFor(() => expect(invitationCreateCall).toHaveBeenCalledWith("test-token", "user-1"));
|
||||
expect(
|
||||
await screen.findByText(`${window.location.origin}/litellm/ui?invitation_id=invite-1`),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -76,7 +76,11 @@ const BulkCreateUsersButton: React.FC<BulkCreateUsersProps> = ({
|
|||
fetchUISettings();
|
||||
|
||||
// Set base URL
|
||||
const base = new URL("/", window.location.href);
|
||||
const base = new URL(window.location.href);
|
||||
const uiPathIndex = base.pathname.indexOf("/ui");
|
||||
base.pathname = uiPathIndex >= 0 ? base.pathname.slice(0, uiPathIndex + 1) : "/";
|
||||
base.search = "";
|
||||
base.hash = "";
|
||||
setBaseUrl(base.toString());
|
||||
}, [accessToken]);
|
||||
|
||||
|
|
@ -364,7 +368,7 @@ const BulkCreateUsersButton: React.FC<BulkCreateUsersProps> = ({
|
|||
if (!uiSettings?.SSO_ENABLED) {
|
||||
// Regular invitation flow
|
||||
const invitationData = await invitationCreateCall(accessToken, user_id);
|
||||
const invitationUrl = new URL(`/ui?invitation_id=${invitationData.id}`, baseUrl).toString();
|
||||
const invitationUrl = new URL(`ui?invitation_id=${invitationData.id}`, baseUrl).toString();
|
||||
|
||||
setParsedData((current) =>
|
||||
current.map((u, i) =>
|
||||
|
|
@ -380,7 +384,7 @@ const BulkCreateUsersButton: React.FC<BulkCreateUsersProps> = ({
|
|||
);
|
||||
} else {
|
||||
// SSO flow - just use the base URL
|
||||
const invitationUrl = new URL("/ui", baseUrl).toString();
|
||||
const invitationUrl = new URL("ui", baseUrl).toString();
|
||||
|
||||
setParsedData((current) =>
|
||||
current.map((u, i) =>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue