This commit is contained in:
devin-ai-integration[bot] 2026-09-27 16:20:46 -04:00 • committed by GitHub
commit 855709580f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 106 additions and 5 deletions

View file

@ -13,7 +13,7 @@ vi.mock("../utils/roles", async (importOriginal) => {
rolesWithWriteAccess: ["admin", "internal"], rolesWithWriteAccess: ["admin", "internal"],
rolesAllowedToViewWriteScopedPages: ["admin", "internal", "admin_viewer"], rolesAllowedToViewWriteScopedPages: ["admin", "internal", "admin_viewer"],
isAdminRole: (role: string) => role === "admin" || role === "admin_viewer", isAdminRole: (role: string) => role === "admin" || role === "admin_viewer",
isUserTeamAdminForAnyTeam: () => false, isUserTeamAdminForAnyTeam: actual.isUserTeamAdminForAnyTeam,
}; };
}); });
@ -24,7 +24,7 @@ vi.mock("next/navigation", () => ({
useRouter: () => ({ push: vi.fn() }), useRouter: () => ({ push: vi.fn() }),
})); }));
const { mockUseAuthorized, mockUseOrganizations } = vi.hoisted(() => { const { mockUseAuthorized, mockUseOrganizations, mockUseTeams } = vi.hoisted(() => {
const mockUseAuthorized = vi.fn(() => ({ const mockUseAuthorized = vi.fn(() => ({
userId: "test-user-id", userId: "test-user-id",
accessToken: "test-access-token", accessToken: "test-access-token",
@ -43,7 +43,13 @@ const { mockUseAuthorized, mockUseOrganizations } = vi.hoisted(() => {
error: null, error: null,
})); }));
return { mockUseAuthorized, mockUseOrganizations }; const mockUseTeams = vi.fn(() => ({
data: [] as Array<{ members_with_roles: Array<{ user_id: string; role: string }> }>,
isLoading: false,
error: null,
}));
return { mockUseAuthorized, mockUseOrganizations, mockUseTeams };
}); });
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({ vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
@ -55,7 +61,7 @@ vi.mock("@/app/(dashboard)/hooks/organizations/useOrganizations", () => ({
})); }));
vi.mock("@/app/(dashboard)/hooks/teams/useTeams", () => ({ vi.mock("@/app/(dashboard)/hooks/teams/useTeams", () => ({
useTeams: () => ({ data: [], isLoading: false, error: null }), useTeams: mockUseTeams,
})); }));
vi.mock("@/app/(dashboard)/hooks/uiConfig/useUIConfig", () => { vi.mock("@/app/(dashboard)/hooks/uiConfig/useUIConfig", () => {
@ -103,6 +109,30 @@ const placementsOf = (page: string): string[] =>
), ),
]); ]);
const teamAdminAuthorization = {
userId: "team-admin-user-id",
accessToken: "test-access-token",
userRole: "internal",
isViewOnly: false,
token: "test-token",
userEmail: "teamadmin@example.com",
premiumUser: false,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
};
const teamMemberAuthorization = {
userId: "team-member-user-id",
accessToken: "test-access-token",
userRole: "internal",
isViewOnly: false,
token: "test-token",
userEmail: "teamuser@example.com",
premiumUser: false,
disabledPersonalKeyCreation: false,
showSSOBanner: false,
};
describe("Sidebar (leftnav)", () => { describe("Sidebar (leftnav)", () => {
const defaultProps = { const defaultProps = {
collapsed: false, collapsed: false,
@ -111,6 +141,12 @@ describe("Sidebar (leftnav)", () => {
afterEach(() => { afterEach(() => {
mockUseAuthorized.mockReset(); mockUseAuthorized.mockReset();
mockUseOrganizations.mockReset(); mockUseOrganizations.mockReset();
mockUseTeams.mockReset();
mockUseTeams.mockReturnValue({
data: [],
isLoading: false,
error: null,
});
mockUseThemeImpl = unbrandedTheme; mockUseThemeImpl = unbrandedTheme;
navState.pathname = "/ui/api-keys"; navState.pathname = "/ui/api-keys";
}); });
@ -515,6 +551,65 @@ describe("Sidebar (leftnav)", () => {
expect(screen.getByText("Organizations")).toBeInTheDocument(); expect(screen.getByText("Organizations")).toBeInTheDocument();
}); });
it("shows Projects to an internal user who administers a team", () => {
mockUseAuthorized.mockReturnValue(teamAdminAuthorization);
mockUseTeams.mockReturnValue({
data: [
{
members_with_roles: [{ user_id: "team-admin-user-id", role: "admin" }],
},
],
isLoading: false,
error: null,
});
renderWithProviders(<Sidebar {...defaultProps} enableProjectsUI />);
expect(screen.getByRole("link", { name: /Projects/ })).toBeInTheDocument();
});
it("hides Projects when the feature flag is disabled for a team admin", () => {
mockUseAuthorized.mockReturnValue(teamAdminAuthorization);
mockUseTeams.mockReturnValue({
data: [
{
members_with_roles: [{ user_id: "team-admin-user-id", role: "admin" }],
},
],
isLoading: false,
error: null,
});
renderWithProviders(<Sidebar {...defaultProps} enableProjectsUI={false} />);
expect(screen.queryByRole("link", { name: /Projects/ })).not.toBeInTheDocument();
});
it("hides Projects from an internal user who is not a team admin", () => {
mockUseAuthorized.mockReturnValue(teamMemberAuthorization);
renderWithProviders(<Sidebar {...defaultProps} enableProjectsUI />);
expect(screen.queryByRole("link", { name: /Projects/ })).not.toBeInTheDocument();
});
it("applies the internal-user page allowlist to Projects for team admins", () => {
mockUseAuthorized.mockReturnValue(teamAdminAuthorization);
mockUseTeams.mockReturnValue({
data: [
{
members_with_roles: [{ user_id: "team-admin-user-id", role: "admin" }],
},
],
isLoading: false,
error: null,
});
renderWithProviders(<Sidebar {...defaultProps} enableProjectsUI enabledPagesInternalUsers={["teams"]} />);
expect(screen.queryByRole("link", { name: /Projects/ })).not.toBeInTheDocument();
});
it("marks the nav item for the current route active", () => { it("marks the nav item for the current route active", () => {
navState.pathname = "/ui/logs"; navState.pathname = "/ui/logs";
renderWithProviders(<Sidebar {...defaultProps} />); renderWithProviders(<Sidebar {...defaultProps} />);

View file

@ -475,7 +475,13 @@ const Sidebar_: React.FC<SidebarProps> = ({
if (!isAdmin && enabledPagesInternalUsers != null) return enabledPagesInternalUsers.includes(item.page); if (!isAdmin && enabledPagesInternalUsers != null) return enabledPagesInternalUsers.includes(item.page);
return true; return true;
} }
if (item.key === "projects" && !enableProjectsUI) return false; if (item.key === "projects") {
if (!enableProjectsUI) return false;
const hasRoleAccess = isAdmin || isOrgAdmin || isTeamAdmin;
if (!hasRoleAccess) return false;
if (!isAdmin && enabledPagesInternalUsers != null) return enabledPagesInternalUsers.includes(item.page);
return true;
}
if ( if (
!isAdmin && !isAdmin &&
item.key === "agents" && item.key === "agents" &&