-
}
- className="shadow-md shadow-indigo-500/20 hover:shadow-indigo-500/50 transition-shadow"
- >
- Join Slack
-
-
}
- >
- Star us on GitHub
-
+
{/* Dark mode is currently a work in progress. To test, you can change 'false' to 'true' below.
Do not set this to true by default until all components are confirmed to support dark mode styles. */}
{false &&
({
teamInfoCall: vi.fn(),
teamMemberDeleteCall: vi.fn(),
@@ -12,12 +12,18 @@ vi.mock("@/components/networking", () => ({
teamMemberUpdateCall: vi.fn(),
teamUpdateCall: vi.fn(),
getGuardrailsList: vi.fn(),
+ getPoliciesList: vi.fn(),
+ getPolicyInfoWithGuardrails: vi.fn(),
fetchMCPAccessGroups: vi.fn(),
getTeamPermissionsCall: vi.fn(),
organizationInfoCall: vi.fn(),
}));
-// Mock hooks used by ModelSelect
+vi.mock("@/components/utils/dataUtils", () => ({
+ copyToClipboard: vi.fn().mockResolvedValue(true),
+ formatNumberWithCommas: vi.fn((value: number) => value.toLocaleString()),
+}));
+
vi.mock("@/app/(dashboard)/hooks/models/useModels", () => ({
useAllProxyModels: vi.fn(),
}));
@@ -34,6 +40,59 @@ vi.mock("@/app/(dashboard)/hooks/users/useCurrentUser", () => ({
useCurrentUser: vi.fn(),
}));
+vi.mock("@/components/team/team_member_view", () => ({
+ default: vi.fn(({ setIsAddMemberModalVisible }) => (
+
+
+
+ )),
+}));
+
+vi.mock("@/components/common_components/user_search_modal", () => ({
+ default: vi.fn(({ isVisible, onCancel, onSubmit }) =>
+ isVisible ? (
+
+
+
+
+ ) : null
+ ),
+}));
+
+vi.mock("@/components/team/EditMembership", () => ({
+ default: vi.fn(({ visible, onCancel, onSubmit }) =>
+ visible ? (
+
+
+
+
+ ) : null
+ ),
+}));
+
+vi.mock("@/components/common_components/DeleteResourceModal", () => ({
+ default: vi.fn(({ isOpen, onCancel, onOk }) =>
+ isOpen ? (
+
+
+
+
+ ) : null
+ ),
+}));
+
+vi.mock("@/components/team/member_permissions", () => ({
+ default: vi.fn(() => Member Permissions
),
+}));
+
+vi.mock("@/components/team/member_permissions", () => ({
+ default: vi.fn(() => Member Permissions
),
+}));
+
import { useAllProxyModels } from "@/app/(dashboard)/hooks/models/useModels";
import { useOrganization } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
import { useTeam } from "@/app/(dashboard)/hooks/teams/useTeams";
@@ -44,9 +103,60 @@ const mockUseTeam = vi.mocked(useTeam);
const mockUseOrganization = vi.mocked(useOrganization);
const mockUseCurrentUser = vi.mocked(useCurrentUser);
+const createMockTeamData = (overrides = {}) => ({
+ team_id: "123",
+ team_info: {
+ team_alias: "Test Team",
+ team_id: "123",
+ organization_id: null,
+ admins: ["admin@test.com"],
+ members: ["user1@test.com"],
+ members_with_roles: [
+ {
+ user_id: "user1@test.com",
+ user_email: "user1@test.com",
+ role: "member",
+ spend: 0,
+ budget_id: "budget1",
+ },
+ ],
+ metadata: {},
+ tpm_limit: null,
+ rpm_limit: null,
+ max_budget: null,
+ budget_duration: null,
+ models: [],
+ blocked: false,
+ spend: 0,
+ max_parallel_requests: null,
+ budget_reset_at: null,
+ model_id: null,
+ litellm_model_table: null,
+ created_at: "2024-01-01T00:00:00Z",
+ team_member_budget_table: null,
+ guardrails: [],
+ policies: [],
+ object_permission: null,
+ ...overrides,
+ },
+ keys: [],
+ team_memberships: [],
+});
+
describe("TeamInfoView", () => {
+ const defaultProps = {
+ teamId: "123",
+ onUpdate: vi.fn(),
+ onClose: vi.fn(),
+ accessToken: "test-token",
+ is_team_admin: true,
+ is_proxy_admin: true,
+ userModels: ["gpt-4", "gpt-3.5-turbo"],
+ editTeam: false,
+ premiumUser: false,
+ };
+
beforeEach(() => {
- // Set up default mock implementations
mockUseAllProxyModels.mockReturnValue({
data: { data: [] },
isLoading: false,
@@ -63,6 +173,14 @@ describe("TeamInfoView", () => {
data: { models: [] },
isLoading: false,
} as any);
+
+ vi.mocked(networking.getGuardrailsList).mockResolvedValue({ guardrails: [] });
+ vi.mocked(networking.getPoliciesList).mockResolvedValue({ policies: [] });
+ vi.mocked(networking.fetchMCPAccessGroups).mockResolvedValue([]);
+ vi.mocked(networking.getTeamPermissionsCall).mockResolvedValue({
+ all_available_permissions: [],
+ team_member_permissions: [],
+ });
});
afterEach(() => {
@@ -70,503 +188,389 @@ describe("TeamInfoView", () => {
});
it("should render", async () => {
- // Mock the team info response
- vi.mocked(networking.teamInfoCall).mockResolvedValue({
- team_id: "123",
- team_info: {
- team_alias: "Test Team",
- team_id: "123",
- organization_id: null,
- admins: ["admin@test.com"],
- members: ["user1@test.com", "user2@test.com"],
- members_with_roles: [
- {
- user_id: "user1@test.com",
- user_email: "user1@test.com",
- role: "member",
- spend: 0,
- budget_id: "budget1",
- },
- ],
- metadata: {},
- tpm_limit: null,
- rpm_limit: null,
- max_budget: null,
- budget_duration: null,
- models: [],
- blocked: false,
- spend: 0,
- max_parallel_requests: null,
- budget_reset_at: null,
- model_id: null,
- litellm_model_table: null,
- created_at: "2024-01-01T00:00:00Z",
- team_member_budget_table: null,
- },
- keys: [],
- team_memberships: [],
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(createMockTeamData());
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ const teamNameElements = screen.queryAllByText("Test Team");
+ expect(teamNameElements.length).toBeGreaterThan(0);
});
+ });
- vi.mocked(networking.getGuardrailsList).mockResolvedValue({ guardrails: [] });
- vi.mocked(networking.fetchMCPAccessGroups).mockResolvedValue([]);
+ it("should display loading state while fetching team data", () => {
+ vi.mocked(networking.teamInfoCall).mockImplementation(() => new Promise(() => { }));
- renderWithProviders(
- {}}
- onClose={() => {}}
- accessToken="123"
- is_team_admin={true}
- is_proxy_admin={true}
- userModels={[]}
- editTeam={false}
- premiumUser={false}
- />,
- );
- await waitFor(
- () => {
- expect(screen.queryByText("User ID")).not.toBeNull();
- },
- // This is a workaround to fix the flaky test issue. TODO: Remove this once we have a better solution.
- { timeout: 10000 },
- );
+ renderWithProviders();
+
+ expect(screen.getByText("Loading...")).toBeInTheDocument();
});
- it("should not show all-proxy-models option when user has no access to it", async () => {
+ it("should display error message when team is not found", async () => {
vi.mocked(networking.teamInfoCall).mockResolvedValue({
team_id: "123",
- team_info: {
- team_alias: "Test Team",
- team_id: "123",
- organization_id: null,
- admins: ["admin@test.com"],
- members: ["user1@test.com", "user2@test.com"],
- members_with_roles: [
- {
- user_id: "user1@test.com",
- user_email: "user1@test.com",
- role: "member",
- spend: 0,
- budget_id: "budget1",
- },
- ],
- metadata: {},
- tpm_limit: null,
- rpm_limit: null,
- max_budget: null,
- budget_duration: null,
- models: ["gpt-4"],
- blocked: false,
- spend: 0,
- max_parallel_requests: null,
- budget_reset_at: null,
- model_id: null,
- litellm_model_table: null,
- created_at: "2024-01-01T00:00:00Z",
- team_member_budget_table: null,
- },
+ team_info: null as any,
keys: [],
team_memberships: [],
});
- vi.mocked(networking.getGuardrailsList).mockResolvedValue({ guardrails: [] });
- vi.mocked(networking.fetchMCPAccessGroups).mockResolvedValue([]);
+ renderWithProviders();
+
+ await waitFor(() => {
+ expect(screen.getByText("Team not found")).toBeInTheDocument();
+ });
+ });
- renderWithProviders(
- {}}
- onClose={() => {}}
- accessToken="123"
- is_team_admin={true}
- is_proxy_admin={true}
- userModels={["gpt-4", "gpt-3.5-turbo"]}
- editTeam={false}
- premiumUser={false}
- />,
+ it("should display budget information in overview", async () => {
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(
+ createMockTeamData({
+ max_budget: 1000,
+ spend: 250.5,
+ budget_duration: "30d",
+ })
);
+ renderWithProviders();
+
await waitFor(() => {
- expect(screen.getAllByText("Test Team")).not.toBeNull();
+ expect(screen.getByText("Budget Status")).toBeInTheDocument();
});
+ });
- const settingsTab = screen.getByRole("tab", { name: "Settings" });
- act(() => {
- fireEvent.click(settingsTab);
- });
+ it("should display guardrails in overview when present", async () => {
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(
+ createMockTeamData({
+ guardrails: ["guardrail1", "guardrail2"],
+ })
+ );
+
+ renderWithProviders();
await waitFor(() => {
- expect(screen.getByText("Team Settings")).toBeInTheDocument();
+ expect(screen.getByText("Guardrails")).toBeInTheDocument();
});
+ });
- const editButton = screen.getByRole("button", { name: "Edit Settings" });
- act(() => {
- fireEvent.click(editButton);
+ it("should display policies in overview when present", async () => {
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(
+ createMockTeamData({
+ policies: ["policy1"],
+ })
+ );
+ vi.mocked(networking.getPolicyInfoWithGuardrails).mockResolvedValue({
+ resolved_guardrails: ["guardrail1"],
});
+ renderWithProviders();
+
await waitFor(() => {
- expect(screen.getByTestId("models-select")).toBeInTheDocument();
+ expect(screen.getByText("Policies")).toBeInTheDocument();
});
+ });
- const allProxyModelsOption = screen.queryByText("All Proxy Models");
- expect(allProxyModelsOption).not.toBeInTheDocument();
- }, 10000); // This is a workaround to fix the flaky test issue. TODO: Remove this once we have a better solution.
+ it("should show members tab when user can edit team", async () => {
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(createMockTeamData());
- it("should only show organization models in dropdown when team is in organization with limited models", async () => {
- const organizationId = "org-123";
- const organizationModels = ["gpt-4", "claude-3-opus"];
- const userModels = ["gpt-4", "gpt-3.5-turbo", "claude-3-opus", "claude-2"];
+ renderWithProviders();
- // Mock all proxy models - should include all user models
- const allProxyModels = userModels.map((id) => ({
- id,
- object: "model",
- created: 1234567890,
- owned_by: "openai",
- }));
+ await waitFor(() => {
+ expect(screen.getByRole("tab", { name: "Members" })).toBeInTheDocument();
+ });
+ });
- mockUseAllProxyModels.mockReturnValue({
- data: { data: allProxyModels },
- isLoading: false,
- } as any);
+ it("should not show members tab when user cannot edit team", async () => {
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(createMockTeamData());
- mockUseCurrentUser.mockReturnValue({
- data: { models: userModels },
- isLoading: false,
- } as any);
+ renderWithProviders();
- const organizationData = {
- organization_id: organizationId,
- organization_name: "Test Organization",
- spend: 0,
- max_budget: null,
- models: organizationModels,
- tpm_limit: null,
- rpm_limit: null,
- members: null,
- };
+ await waitFor(() => {
+ const teamNameElements = screen.queryAllByText("Test Team");
+ expect(teamNameElements.length).toBeGreaterThan(0);
+ });
- mockUseOrganization.mockReturnValue({
- data: organizationData,
- isLoading: false,
- } as any);
+ expect(screen.queryByRole("tab", { name: "Members" })).not.toBeInTheDocument();
+ });
- vi.mocked(networking.teamInfoCall).mockResolvedValue({
- team_id: "123",
- team_info: {
- team_alias: "Test Team",
- team_id: "123",
- organization_id: organizationId,
- admins: ["admin@test.com"],
- members: ["user1@test.com"],
- members_with_roles: [
- {
- user_id: "user1@test.com",
- user_email: "user1@test.com",
- role: "member",
- spend: 0,
- budget_id: "budget1",
- },
- ],
- metadata: {},
- tpm_limit: null,
- rpm_limit: null,
- max_budget: null,
- budget_duration: null,
- models: ["gpt-4"],
- blocked: false,
- spend: 0,
- max_parallel_requests: null,
- budget_reset_at: null,
- model_id: null,
- litellm_model_table: null,
- created_at: "2024-01-01T00:00:00Z",
- team_member_budget_table: null,
- },
- keys: [],
- team_memberships: [],
- });
+ it("should show settings tab when user can edit team", async () => {
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(createMockTeamData());
- vi.mocked(networking.organizationInfoCall).mockResolvedValue(organizationData);
+ renderWithProviders();
- vi.mocked(networking.getGuardrailsList).mockResolvedValue({ guardrails: [] });
- vi.mocked(networking.fetchMCPAccessGroups).mockResolvedValue([]);
+ await waitFor(() => {
+ expect(screen.getByRole("tab", { name: "Settings" })).toBeInTheDocument();
+ });
+ });
- renderWithProviders(
- {}}
- onClose={() => {}}
- accessToken="123"
- is_team_admin={true}
- is_proxy_admin={true}
- userModels={userModels}
- editTeam={false}
- premiumUser={false}
- />,
- );
+ it("should navigate to settings tab when clicked", async () => {
+ const user = userEvent.setup();
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(createMockTeamData());
+
+ renderWithProviders();
await waitFor(() => {
- expect(screen.getAllByText("Test Team")).not.toBeNull();
+ const teamNameElements = screen.queryAllByText("Test Team");
+ expect(teamNameElements.length).toBeGreaterThan(0);
});
const settingsTab = screen.getByRole("tab", { name: "Settings" });
- act(() => {
- fireEvent.click(settingsTab);
- });
+ await user.click(settingsTab);
await waitFor(() => {
expect(screen.getByText("Team Settings")).toBeInTheDocument();
});
+ });
+
+ it("should open edit mode when edit button is clicked", async () => {
+ const user = userEvent.setup();
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(createMockTeamData());
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ const teamNameElements = screen.queryAllByText("Test Team");
+ expect(teamNameElements.length).toBeGreaterThan(0);
+ });
+
+ const settingsTab = screen.getByRole("tab", { name: "Settings" });
+ await user.click(settingsTab);
+
+ await waitFor(() => {
+ expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument();
+ });
const editButton = screen.getByRole("button", { name: "Edit Settings" });
- act(() => {
- fireEvent.click(editButton);
+ await user.click(editButton);
+
+ await waitFor(() => {
+ expect(screen.getByLabelText("Team Name")).toBeInTheDocument();
});
+ });
+
+ it("should close edit mode when cancel button is clicked", async () => {
+ const user = userEvent.setup();
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(createMockTeamData());
+
+ renderWithProviders();
await waitFor(() => {
- expect(screen.getByTestId("models-select")).toBeInTheDocument();
+ const teamNameElements = screen.queryAllByText("Test Team");
+ expect(teamNameElements.length).toBeGreaterThan(0);
});
- // Find the Ant Design Select selector element to open the dropdown
- // The data-testid is on the Select component, we need to find the selector inside it
- const modelsSelectElement = screen.getByTestId("models-select");
- const selectSelector = modelsSelectElement.querySelector(".ant-select-selector");
- expect(selectSelector).toBeTruthy();
+ const settingsTab = screen.getByRole("tab", { name: "Settings" });
+ await user.click(settingsTab);
- // Open the dropdown by clicking on the selector
- act(() => {
- fireEvent.mouseDown(selectSelector!);
+ await waitFor(() => {
+ expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument();
});
- // Wait for dropdown to open - Ant Design renders options in a portal
- await waitFor(
- () => {
- const dropdownOptions = document.querySelectorAll(".ant-select-item-option");
- expect(dropdownOptions.length).toBeGreaterThan(0);
- },
- { timeout: 5000 },
- );
+ const editButton = screen.getByRole("button", { name: "Edit Settings" });
+ await user.click(editButton);
- const dropdownOptions = document.querySelectorAll(".ant-select-item-option");
- const optionTexts = Array.from(dropdownOptions).map((option) => option.textContent?.trim() || "");
+ await waitFor(() => {
+ expect(screen.getByLabelText("Team Name")).toBeInTheDocument();
+ });
+
+ const cancelButton = screen.getByRole("button", { name: "Cancel" });
+ await user.click(cancelButton);
+
+ await waitFor(() => {
+ expect(screen.queryByLabelText("Team Name")).not.toBeInTheDocument();
+ });
+ });
- organizationModels.forEach((model) => {
- expect(optionTexts).toContain(model);
+ it("should call onClose when back button is clicked", async () => {
+ const user = userEvent.setup();
+ const onClose = vi.fn();
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(createMockTeamData());
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ const teamNameElements = screen.queryAllByText("Test Team");
+ expect(teamNameElements.length).toBeGreaterThan(0);
});
- const modelsNotInOrganization = userModels.filter((m) => !organizationModels.includes(m));
- modelsNotInOrganization.forEach((model) => {
- expect(optionTexts).not.toContain(model);
+ const backButton = screen.getByRole("button", { name: /back to teams/i });
+ await user.click(backButton);
+
+ expect(onClose).toHaveBeenCalled();
+ });
+
+ it("should copy team ID to clipboard when copy button is clicked", async () => {
+ const user = userEvent.setup();
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(createMockTeamData());
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ const teamNameElements = screen.queryAllByText("Test Team");
+ expect(teamNameElements.length).toBeGreaterThan(0);
});
- }, 10000);
+
+ const copyButtons = screen.getAllByRole("button");
+ const copyButton = copyButtons.find((btn) => btn.querySelector("svg"));
+ expect(copyButton).toBeTruthy();
+
+ if (copyButton) {
+ await user.click(copyButton);
+ }
+ });
it("should disable secret manager settings for non-premium users", async () => {
- const teamResponse = {
- team_id: "123",
- team_info: {
- team_alias: "Test Team",
- team_id: "123",
- organization_id: null,
- admins: ["admin@test.com"],
- members: [],
- members_with_roles: [],
+ const user = userEvent.setup();
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(
+ createMockTeamData({
metadata: {
secret_manager_settings: { provider: "aws", secret_id: "abc" },
},
- tpm_limit: null,
- rpm_limit: null,
- max_budget: null,
- budget_duration: null,
- models: ["gpt-4"],
- blocked: false,
- spend: 0,
- max_parallel_requests: null,
- budget_reset_at: null,
- model_id: null,
- litellm_model_table: null,
- created_at: "2024-01-01T00:00:00Z",
- team_member_budget_table: null,
- },
- keys: [],
- team_memberships: [],
- };
+ })
+ );
- vi.mocked(networking.teamInfoCall).mockResolvedValue(teamResponse as any);
- vi.mocked(networking.getGuardrailsList).mockResolvedValue({ guardrails: [] });
- vi.mocked(networking.fetchMCPAccessGroups).mockResolvedValue([]);
+ renderWithProviders();
- renderWithProviders(
- {}}
- onClose={() => {}}
- accessToken="123"
- is_team_admin={true}
- is_proxy_admin={true}
- userModels={["gpt-4"]}
- editTeam={false}
- premiumUser={false}
- />,
- );
+ await waitFor(() => {
+ const teamNameElements = screen.queryAllByText("Test Team");
+ expect(teamNameElements.length).toBeGreaterThan(0);
+ });
+
+ const settingsTab = screen.getByRole("tab", { name: "Settings" });
+ await user.click(settingsTab);
- const settingsTab = await screen.findByRole("tab", { name: "Settings" });
- act(() => fireEvent.click(settingsTab));
+ await waitFor(() => {
+ expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument();
+ });
- const editButton = await screen.findByRole("button", { name: "Edit Settings" });
- act(() => fireEvent.click(editButton));
+ const editButton = screen.getByRole("button", { name: "Edit Settings" });
+ await user.click(editButton);
const secretField = await screen.findByPlaceholderText(
- '{"namespace": "admin", "mount": "secret", "path_prefix": "litellm"}',
+ '{"namespace": "admin", "mount": "secret", "path_prefix": "litellm"}'
);
expect(secretField).toBeDisabled();
- expect(secretField).toHaveValue(JSON.stringify(teamResponse.team_info.metadata.secret_manager_settings, null, 2));
- }, 10000);
+ });
- it("should allow premium users to update secret manager settings", async () => {
- const teamResponse = {
- team_id: "123",
- team_info: {
- team_alias: "Test Team",
- team_id: "123",
- organization_id: null,
- admins: ["admin@test.com"],
- members: [],
- members_with_roles: [],
+ it("should allow premium users to edit secret manager settings", async () => {
+ const user = userEvent.setup();
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(
+ createMockTeamData({
metadata: {
secret_manager_settings: { provider: "aws", secret_id: "abc" },
},
- tpm_limit: null,
- rpm_limit: null,
- max_budget: null,
- budget_duration: null,
- models: ["gpt-4"],
- blocked: false,
- spend: 0,
- max_parallel_requests: null,
- budget_reset_at: null,
- model_id: null,
- litellm_model_table: null,
- created_at: "2024-01-01T00:00:00Z",
- team_member_budget_table: null,
- },
- keys: [],
- team_memberships: [],
- };
-
- vi.mocked(networking.teamInfoCall).mockResolvedValue(teamResponse as any);
- vi.mocked(networking.getGuardrailsList).mockResolvedValue({ guardrails: [] });
- vi.mocked(networking.fetchMCPAccessGroups).mockResolvedValue([]);
- vi.mocked(networking.teamUpdateCall).mockResolvedValue({ data: teamResponse.team_info, team_id: "123" } as any);
-
- renderWithProviders(
- {}}
- onClose={() => {}}
- accessToken="123"
- is_team_admin={true}
- is_proxy_admin={true}
- userModels={["gpt-4"]}
- editTeam={false}
- premiumUser={true}
- />,
+ })
);
+ vi.mocked(networking.teamUpdateCall).mockResolvedValue({ data: {}, team_id: "123" } as any);
- const settingsTab = await screen.findByRole("tab", { name: "Settings" });
- act(() => fireEvent.click(settingsTab));
+ renderWithProviders();
- const editButton = await screen.findByRole("button", { name: "Edit Settings" });
- act(() => fireEvent.click(editButton));
+ await waitFor(() => {
+ const teamNameElements = screen.queryAllByText("Test Team");
+ expect(teamNameElements.length).toBeGreaterThan(0);
+ });
+
+ const settingsTab = screen.getByRole("tab", { name: "Settings" });
+ await user.click(settingsTab);
+
+ await waitFor(() => {
+ expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument();
+ });
+
+ const editButton = screen.getByRole("button", { name: "Edit Settings" });
+ await user.click(editButton);
const secretField = await screen.findByPlaceholderText(
- '{"namespace": "admin", "mount": "secret", "path_prefix": "litellm"}',
+ '{"namespace": "admin", "mount": "secret", "path_prefix": "litellm"}'
);
expect(secretField).not.toBeDisabled();
+ });
- act(() => {
- fireEvent.change(secretField, { target: { value: '{"provider":"azure","secret_id":"xyz"}' } });
+ it("should add team member when form is submitted", async () => {
+ const user = userEvent.setup();
+ const onUpdate = vi.fn();
+ const teamData = createMockTeamData();
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(teamData);
+ vi.mocked(networking.teamMemberAddCall).mockResolvedValue({} as any);
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ const teamNameElements = screen.queryAllByText("Test Team");
+ expect(teamNameElements.length).toBeGreaterThan(0);
});
- const saveButton = await screen.findByRole("button", { name: "Save Changes" });
- act(() => fireEvent.click(saveButton));
+ const membersTab = screen.getByRole("tab", { name: "Members" });
+ await user.click(membersTab);
await waitFor(() => {
- expect(networking.teamUpdateCall).toHaveBeenCalled();
+ expect(screen.getByRole("button", { name: "Add Member" })).toBeInTheDocument();
});
- const payload = vi.mocked(networking.teamUpdateCall).mock.calls[0][1];
- expect(payload.metadata.secret_manager_settings).toEqual({ provider: "azure", secret_id: "xyz" });
- }, 10000);
+ const addButton = screen.getByRole("button", { name: "Add Member" });
+ await user.click(addButton);
- it("should include vector stores in object_permission when updating team", async () => {
- const teamResponse = {
- team_id: "123",
- team_info: {
- team_alias: "Test Team",
- team_id: "123",
- organization_id: null,
- admins: ["admin@test.com"],
- members: [],
- members_with_roles: [],
- metadata: {},
- tpm_limit: null,
- rpm_limit: null,
- max_budget: null,
- budget_duration: null,
- models: ["gpt-4"],
- blocked: false,
- spend: 0,
- max_parallel_requests: null,
- budget_reset_at: null,
- model_id: null,
- litellm_model_table: null,
- created_at: "2024-01-01T00:00:00Z",
- team_member_budget_table: null,
- object_permission: {
- vector_stores: ["store1", "store2"],
- },
- },
- keys: [],
- team_memberships: [],
- };
+ await waitFor(() => {
+ expect(screen.getByRole("button", { name: "Submit" })).toBeInTheDocument();
+ });
- vi.mocked(networking.teamInfoCall).mockResolvedValue(teamResponse as any);
- vi.mocked(networking.getGuardrailsList).mockResolvedValue({ guardrails: [] });
- vi.mocked(networking.fetchMCPAccessGroups).mockResolvedValue([]);
- vi.mocked(networking.teamUpdateCall).mockResolvedValue({ data: teamResponse.team_info, team_id: "123" } as any);
-
- renderWithProviders(
- {}}
- onClose={() => {}}
- accessToken="123"
- is_team_admin={true}
- is_proxy_admin={true}
- userModels={["gpt-4"]}
- editTeam={false}
- premiumUser={true}
- />,
+ const submitButton = screen.getByRole("button", { name: "Submit" });
+ await user.click(submitButton);
+
+ await waitFor(() => {
+ expect(networking.teamMemberAddCall).toHaveBeenCalled();
+ });
+ });
+
+ it("should display team member budget information when present", async () => {
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(
+ createMockTeamData({
+ team_member_budget_table: {
+ max_budget: 500,
+ budget_duration: "30d",
+ tpm_limit: 5000,
+ rpm_limit: 50,
+ },
+ })
);
- const settingsTab = await screen.findByRole("tab", { name: "Settings" });
- act(() => fireEvent.click(settingsTab));
+ renderWithProviders();
- const editButton = await screen.findByRole("button", { name: "Edit Settings" });
- act(() => fireEvent.click(editButton));
+ await waitFor(() => {
+ expect(screen.getByText("Budget Status")).toBeInTheDocument();
+ });
+ });
- // Verify that Vector Stores field is present
- expect(screen.getByLabelText("Vector Stores")).toBeInTheDocument();
+ it("should display virtual keys information", async () => {
+ vi.mocked(networking.teamInfoCall).mockResolvedValue({
+ ...createMockTeamData(),
+ keys: [
+ { user_id: "user1", token: "key1" },
+ { token: "key2" },
+ ],
+ });
- const saveButton = await screen.findByRole("button", { name: "Save Changes" });
- act(() => fireEvent.click(saveButton));
+ renderWithProviders();
await waitFor(() => {
- expect(networking.teamUpdateCall).toHaveBeenCalled();
+ expect(screen.getByText("Virtual Keys")).toBeInTheDocument();
});
+ });
+
+ it("should display object permissions when present", async () => {
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(
+ createMockTeamData({
+ object_permission: {
+ object_permission_id: "perm-1",
+ mcp_servers: ["server1"],
+ vector_stores: ["store1"],
+ },
+ })
+ );
+
+ renderWithProviders();
- const payload = vi.mocked(networking.teamUpdateCall).mock.calls[0][1];
- expect(payload.object_permission.vector_stores).toEqual(["store1", "store2"]);
- }, 10000);
+ await waitFor(() => {
+ const teamNameElements = screen.queryAllByText("Test Team");
+ expect(teamNameElements.length).toBeGreaterThan(0);
+ });
+ });
});
diff --git a/ui/litellm-dashboard/src/components/team/team_info.tsx b/ui/litellm-dashboard/src/components/team/team_info.tsx
index 193d056fdd4..34ff903c864 100644
--- a/ui/litellm-dashboard/src/components/team/team_info.tsx
+++ b/ui/litellm-dashboard/src/components/team/team_info.tsx
@@ -462,6 +462,7 @@ const TeamInfoView: React.FC = ({
...parsedMetadata,
guardrails: values.guardrails || [],
logging: values.logging_settings || [],
+ disable_global_guardrails: values.disable_global_guardrails || false,
...(secretManagerSettings !== undefined ? { secret_manager_settings: secretManagerSettings } : {}),
},
policies: values.policies || [],
@@ -572,11 +573,10 @@ const TeamInfoView: React.FC = ({
size="small"
icon={copiedStates["team-id"] ? : }
onClick={() => copyToClipboard(info.team_id, "team-id")}
- className={`left-2 z-10 transition-all duration-200 ${
- copiedStates["team-id"]
- ? "text-green-600 bg-green-50 border-green-200"
- : "text-gray-500 hover:text-gray-700 hover:bg-gray-100"
- }`}
+ className={`left-2 z-10 transition-all duration-200 ${copiedStates["team-id"]
+ ? "text-green-600 bg-green-50 border-green-200"
+ : "text-gray-500 hover:text-gray-700 hover:bg-gray-100"
+ }`}
/>
@@ -588,10 +588,10 @@ const TeamInfoView: React.FC