mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
fix(ui): stop useCan from firing /organization/list on every page
useCan wraps useIsOrgAdmin which unconditionally called useOrganizations, so every gated page hit /organization/list even for capabilities that only look at the session role. That broke three page integration tests that assert fetch is not called for a non-admin, and it broke three cost-optimization tests where the extra useQuery threw because they rendered without a QueryClientProvider. Skip the fetch when the capability isn't in ORG_ADMIN_CAPABILITIES, and wrap the three cost-optimization test files with the shared providers helper so the still-necessary useQuery has a client. Co-authored-by: Krrish Dholakia <krrish-berri-2@users.noreply.github.com>
This commit is contained in:
parent
6f36bee6ba
commit
f8f40c4284
7 changed files with 47 additions and 13 deletions
|
|
@ -1,5 +1,13 @@
|
|||
import { fireEvent, render, waitFor } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { fireEvent, waitFor } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { renderWithProviders, testQueryClient } from "../../../../../tests/test-utils";
|
||||
|
||||
const { useAuthorizedMock } = vi.hoisted(() => ({ useAuthorizedMock: vi.fn() }));
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: useAuthorizedMock,
|
||||
}));
|
||||
|
||||
const mockUserDailyActivityCall = vi.fn();
|
||||
|
||||
|
|
@ -36,10 +44,15 @@ const singlePage = {
|
|||
};
|
||||
|
||||
describe("CostOptimizationView daily activity", () => {
|
||||
beforeEach(() => {
|
||||
testQueryClient.clear();
|
||||
useAuthorizedMock.mockReturnValue({ accessToken: "test-token", userId: "u1", userRole: "proxy_admin" });
|
||||
});
|
||||
|
||||
it("fetches daily activity once for the page and shares it with every tab that needs it", async () => {
|
||||
mockUserDailyActivityCall.mockResolvedValue(singlePage);
|
||||
|
||||
const { getByRole, getByTestId } = render(
|
||||
const { getByRole, getByTestId } = renderWithProviders(
|
||||
<CostOptimizationView accessToken="test-token" userId="u1" userRole="proxy_admin" />,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { fireEvent, render } from "@testing-library/react";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { renderWithProviders, testQueryClient } from "../../../../../tests/test-utils";
|
||||
|
||||
const { useAuthorizedMock } = vi.hoisted(() => ({ useAuthorizedMock: vi.fn() }));
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
|
|
@ -19,11 +21,12 @@ import CostOptimizationView from "./CostOptimizationView";
|
|||
|
||||
const renderView = (userRole = "Admin") => {
|
||||
useAuthorizedMock.mockReturnValue({ accessToken: "test-token", userId: "u1", userRole });
|
||||
return render(<CostOptimizationView accessToken="test-token" userId="u1" userRole={userRole} />);
|
||||
return renderWithProviders(<CostOptimizationView accessToken="test-token" userId="u1" userRole={userRole} />);
|
||||
};
|
||||
|
||||
describe("CostOptimizationView", () => {
|
||||
beforeEach(() => {
|
||||
testQueryClient.clear();
|
||||
useAuthorizedMock.mockReturnValue({ accessToken: "test-token", userId: "u1", userRole: "Admin" });
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { render } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ToolSpendResponse } from "@/components/networking";
|
||||
|
||||
import type { DailyData, SpendMetrics } from "@/components/UsagePage/types";
|
||||
|
||||
import { renderWithProviders, testQueryClient } from "../../../../../tests/test-utils";
|
||||
|
||||
const mockGetToolSpend = vi.fn();
|
||||
|
||||
const { useAuthorizedMock } = vi.hoisted(() => ({ useAuthorizedMock: vi.fn() }));
|
||||
|
|
@ -106,7 +107,7 @@ const renderWith = (results: DailyData[], options: RenderOptions = {}) => {
|
|||
} = options;
|
||||
mockGetToolSpend.mockResolvedValue(toolSpend);
|
||||
useAuthorizedMock.mockReturnValue({ accessToken: "test-token", userId: "u1", userRole });
|
||||
return render(
|
||||
return renderWithProviders(
|
||||
<UsageTab
|
||||
accessToken="test-token"
|
||||
activity={{
|
||||
|
|
@ -124,6 +125,7 @@ const readSeries = (element: HTMLElement) => JSON.parse(element.getAttribute("da
|
|||
|
||||
describe("UsageTab", () => {
|
||||
beforeEach(() => {
|
||||
testQueryClient.clear();
|
||||
mockGetToolSpend.mockReset();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,14 @@ export interface OrganizationListFilters {
|
|||
org_alias?: string | null;
|
||||
}
|
||||
|
||||
export const useOrganizations = (filters?: OrganizationListFilters): UseQueryResult<Organization[]> => {
|
||||
export interface UseOrganizationsOptions {
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export const useOrganizations = (
|
||||
filters?: OrganizationListFilters,
|
||||
options?: UseOrganizationsOptions,
|
||||
): UseQueryResult<Organization[]> => {
|
||||
const { accessToken, userId, userRole } = useAuthorized();
|
||||
const orgId = filters?.org_id || null;
|
||||
const orgAlias = filters?.org_alias || null;
|
||||
|
|
@ -21,7 +28,7 @@ export const useOrganizations = (filters?: OrganizationListFilters): UseQueryRes
|
|||
: {},
|
||||
),
|
||||
queryFn: async () => await organizationListCall(accessToken!, orgId, orgAlias),
|
||||
enabled: Boolean(accessToken && userId && userRole),
|
||||
enabled: (options?.enabled ?? true) && Boolean(accessToken && userId && userRole),
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
"use client";
|
||||
|
||||
import { hasCapability, type Capability } from "@/utils/capabilities";
|
||||
import { capabilityRequiresOrgAdmin, hasCapability, type Capability } from "@/utils/capabilities";
|
||||
|
||||
import useAuthorized from "./useAuthorized";
|
||||
import useIsOrgAdmin from "./useIsOrgAdmin";
|
||||
|
||||
const useCan = (capability: Capability): boolean => {
|
||||
const { userRole } = useAuthorized();
|
||||
const isOrgAdmin = useIsOrgAdmin();
|
||||
const isOrgAdmin = useIsOrgAdmin({ enabled: capabilityRequiresOrgAdmin(capability) });
|
||||
return hasCapability(userRole, capability, isOrgAdmin);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,16 @@ import { isOrgAdminForAnyOrg, isOrgAdminSessionRole } from "@/utils/roles";
|
|||
import { useOrganizations } from "./organizations/useOrganizations";
|
||||
import useAuthorized from "./useAuthorized";
|
||||
|
||||
const useIsOrgAdmin = (): boolean => {
|
||||
export interface UseIsOrgAdminOptions {
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
const useIsOrgAdmin = (options?: UseIsOrgAdminOptions): boolean => {
|
||||
const { userId, userRole } = useAuthorized();
|
||||
const { data: organizations } = useOrganizations();
|
||||
const { data: organizations } = useOrganizations(undefined, options);
|
||||
if (options?.enabled === false) {
|
||||
return isOrgAdminSessionRole(userRole);
|
||||
}
|
||||
return isOrgAdminSessionRole(userRole) || isOrgAdminForAnyOrg(organizations, userId);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ export type Capability = keyof typeof CAPABILITY_ROLES;
|
|||
|
||||
const ORG_ADMIN_CAPABILITIES: ReadonlySet<Capability> = new Set<Capability>(["viewDeletedTeams"]);
|
||||
|
||||
export const capabilityRequiresOrgAdmin = (capability: Capability): boolean => ORG_ADMIN_CAPABILITIES.has(capability);
|
||||
|
||||
export const hasCapability = (
|
||||
userRole: string | null | undefined,
|
||||
capability: Capability,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue