From c8e59e4bfe8081de3671878a2e07e7f81ad55dcc Mon Sep 17 00:00:00 2001 From: Ibra Date: Thu, 5 Feb 2026 16:01:48 -0600 Subject: [PATCH 1/9] Removal of API token componenets if the feature is disabled BED-7137 --- .../src/views/UserProfile/UserProfile.tsx | 28 +++++++++++-------- .../src/views/Users/UserActionsMenu.tsx | 26 +++++++++-------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.tsx b/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.tsx index 4058fc97489..fb57e93d7c7 100644 --- a/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.tsx +++ b/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.tsx @@ -31,6 +31,7 @@ import { import { useSelf } from '../../hooks/useBloodHoundUsers'; import { useNotifications } from '../../providers'; import { apiClient, getUsername } from '../../utils'; +import { useAPITokensConfiguration } from '../../hooks'; const UserProfile = () => { const { addNotification } = useNotifications(); @@ -45,6 +46,7 @@ const UserProfile = () => { const [disable2FASecret, setDisable2FASecret] = useState(''); const getSelfQuery = useSelf(); + const apiTokensEnabled = useAPITokensConfiguration() const updateUserPasswordMutation = useMutation( ({ userId, ...payload }: { userId: string } & PutUserAuthSecretRequest) => @@ -131,19 +133,21 @@ const UserProfile = () => { Authentication - - - API Key Management - - - + {apiTokensEnabled && ( + + + API Key Management + + + + - + )} {user && user.sso_provider_id === null && ( <> diff --git a/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.tsx b/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.tsx index 6a8615dbd3b..3f31672603d 100644 --- a/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.tsx +++ b/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.tsx @@ -29,6 +29,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IconButton, ListItemIcon, ListItemText, Menu, MenuItem, MenuProps } from '@mui/material'; import withStyles from '@mui/styles/withStyles'; import React from 'react'; +import { useAPITokensConfiguration } from '../../hooks'; const StyledMenu = withStyles({ paper: { @@ -87,7 +88,7 @@ const UserActionsMenu: React.FC = ({ /* Hooks */ const [anchorEl, setAnchorEl] = React.useState(null); - + const apiTokensEnabled = useAPITokensConfiguration() /* Event Handlers */ const handleOnOpen: React.MouseEventHandler = (event) => { @@ -186,17 +187,18 @@ const UserActionsMenu: React.FC = ({ )} - ) => { - onManageUserTokens(e); - setAnchorEl(null); - }}> - - - - - - + {apiTokensEnabled && ( + ) => { + onManageUserTokens(e); + setAnchorEl(null); + }}> + + + + // + + )} {showDisableMfaButton && ( ) => { From b9bd1896cd5c41a80fe6ac3ff7e00341909823d0 Mon Sep 17 00:00:00 2001 From: Ibra Date: Mon, 9 Feb 2026 17:39:57 -0600 Subject: [PATCH 2/9] Adding semi-working tests. Need to add for managment page --- .../views/UserProfile/UserProfile.test.tsx | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx b/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx index 0932ee9bc72..dfeb4e34942 100644 --- a/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx +++ b/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx @@ -21,13 +21,37 @@ import UserProfile from './UserProfile'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; +import {ConfigurationKey} from "js-client-library"; + +const setInitialServerState = (savedConfigurationValue?: boolean) => { + return { + isAPITokensConfigurationEnabled: savedConfigurationValue || false, + }; +}; + +let serverState = setInitialServerState(); const server = setupServer( rest.get(`/api/v2/self`, (req, res) => { return res(); - }) + }), + rest.get(`/api/v2/config`, async (_req, res, ctx) => { + return res( + ctx.json({ + data: [ + { + key: ConfigurationKey.APITokens, + value: { + enabled: serverState.isAPITokensConfigurationEnabled, + }, + }, + ], + }) + ); + }), ); + beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); @@ -165,4 +189,31 @@ describe('UserProfile', () => { expect(within(modal).getByText('Configure Multi-Factor Authentication')).toBeInTheDocument(); }); }); + + + describe('When Api Keys Param enabled is false', () => { + const savedConfigurationValue = false; + serverState = setInitialServerState(savedConfigurationValue); + + + it('should not display api key management button', () => { + const apiKeyManagementButton2 = screen.queryByTestId('my-profile_button-api-key-management'); + expect(apiKeyManagementButton2).not.toBeInTheDocument(); + }); + }); + + + describe('When Api Keys Param enabled is true', () => { + const savedConfigurationValue = true; + serverState = setInitialServerState(savedConfigurationValue); + + + it('should display api key management button', () => { + const apiKeyManagementButton = screen.queryByTestId('my-profile_button-api-key-management'); + expect(apiKeyManagementButton).toBeInTheDocument(); + }); + }); + + + }); From 0e4b4c14a0f41a2912fe4348b04557ac48e4d677 Mon Sep 17 00:00:00 2001 From: Ibra Date: Thu, 19 Feb 2026 15:24:18 -0600 Subject: [PATCH 3/9] Working Test --- .../views/UserProfile/UserProfile.test.tsx | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx b/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx index c167da0c49e..5180cd5838e 100644 --- a/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx +++ b/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx @@ -21,9 +21,9 @@ import UserProfile from './UserProfile'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import {ConfigurationKey} from "js-client-library"; -import {QueryClient} from "react-query"; -import {configurationKeys} from "../../hooks"; +import { ConfigurationKey } from "js-client-library"; +import { QueryClient } from "react-query"; +import { configurationKeys } from "../../hooks"; const CONFIG_ENABLED_RESPONSE = { data: [ @@ -58,7 +58,6 @@ const server = setupServer( }), ); - beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); @@ -196,30 +195,32 @@ describe('UserProfile', () => { expect(within(modal).getByText('Configure Multi-Factor Authentication')).toBeInTheDocument(); }); }); +}); +describe('Api Keys', () => { + it('should display api key management button', async () => { - describe.only('Api Keys', () => { - it('should display api key management button', async () => { - render() - const apiKeyManagementButton = await screen.findByRole('button', { name: 'API Key Management' }); - expect(apiKeyManagementButton).toBeInTheDocument(); - }); + render() + const apiKeyManagementButton = await screen.findByRole('button', { name: 'API Key Management' }); + expect(apiKeyManagementButton).toBeInTheDocument(); - it('should not display api key management button', async () => { - server.use( - rest.get(`/api/v2/config`, async (_req, res, ctx) => { - return res.once( - ctx.json(CONFIG_DISABLED_RESPONSE) - ); - }), - ); - - const queryClient = new QueryClient() - render(, {queryClient}) - - queryClient.invalidateQueries(configurationKeys.all) - const apiKeyManagementButton = await screen.queryByRole('button', { name: 'API Key Management' }); - expect(apiKeyManagementButton).not.toBeInTheDocument(); - }); + }); + + it('should not display api key management button', async () => { + + server.use( + rest.get(`/api/v2/config`, async (_req, res, ctx) => { + return res( + ctx.json(CONFIG_DISABLED_RESPONSE) + ); + }), + ); + + const queryClient = new QueryClient() + render(, {queryClient}) + + await queryClient.invalidateQueries(configurationKeys.all) + const apiKeyManagementButton = screen.queryByRole('button', { name: 'API Key Management' }); + expect(apiKeyManagementButton).not.toBeInTheDocument(); }); }); From 551b1606a9f35318fec844cf76bb7a2e79ee8023 Mon Sep 17 00:00:00 2001 From: Ibra Date: Fri, 20 Feb 2026 12:38:05 -0600 Subject: [PATCH 4/9] Update logging and return from useConfiguration --- .../javascript/bh-shared-ui/src/hooks/useConfiguration.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/javascript/bh-shared-ui/src/hooks/useConfiguration.ts b/packages/javascript/bh-shared-ui/src/hooks/useConfiguration.ts index e7d1e85711c..41fa986e007 100644 --- a/packages/javascript/bh-shared-ui/src/hooks/useConfiguration.ts +++ b/packages/javascript/bh-shared-ui/src/hooks/useConfiguration.ts @@ -29,14 +29,10 @@ export const configurationKeys = { }; const getConfiguration = (options?: RequestOptions) => { - return apiClient.getConfiguration(options).then((res) => { - console.log(JSON.stringify(res.data)) - return res.data - }); + return apiClient.getConfiguration(options).then((res) => res.data); }; export const useGetConfiguration = () => { - console.log('useGetConfig hook') return useQuery(configurationKeys.all, ({ signal }) => getConfiguration({ signal }), { refetchOnWindowFocus: false, }); From 67245f308d8a1478186f0daecf703c11cc44f1e8 Mon Sep 17 00:00:00 2001 From: Ibra Date: Tue, 24 Feb 2026 15:33:49 -0600 Subject: [PATCH 5/9] Tests need review --- .../src/views/Users/UserActionsMenu.test.tsx | 229 ++++++++++++++++++ .../src/views/Users/UserActionsMenu.tsx | 1 + 2 files changed, 230 insertions(+) create mode 100644 packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx diff --git a/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx b/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx new file mode 100644 index 00000000000..ed0bf1fcef2 --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx @@ -0,0 +1,229 @@ +import {ConfigurationKey} from "js-client-library"; +import {setupServer} from "msw/node"; +import {rest} from "msw"; +import {render, screen} from "../../test-utils"; +import {QueryClient} from "react-query"; +import {configurationKeys} from "../../hooks"; +import UserActionsMenu from "./UserActionsMenu"; + + +const CONFIG_ENABLED_RESPONSE = { + data: [ + { + key: ConfigurationKey.APITokens, + value: { + enabled: true, + }, + }, + ], +} + +const CONFIG_DISABLED_RESPONSE = { + data: [ + { + key: ConfigurationKey.APITokens, + value: { + enabled: false, + }, + }, + ], +} + +const noop = () => undefined; + +interface testProps { + userId: string; + onOpen: (e: any, userId: string) => any; + showPasswordOptions: boolean; + showAuthMgmtButtons: boolean; + showDisableMfaButton: boolean; + userDisabled: boolean; + onUpdateUser: (e: any) => any; + onDisableUser: (e: any) => any; + onEnableUser: (e: any) => any; + onDeleteUser: (e: any) => any; + onUpdateUserPassword: (e: any) => any; + onExpireUserPassword: (e: any) => any; + onManageUserTokens: (e: any) => any; + onDisableUserMfa: (e: any) => any; + index: number; +} + + +const server = setupServer( + rest.get(`/api/v2/config`, async (_req, res, ctx) => { + return res( + ctx.json(CONFIG_ENABLED_RESPONSE) + ); + }), +); + +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); + +describe('Api Keys', () => { + + const setSelectedUserId = (id: string) => { + return noop(); + } + + const user = { + id: "testID", + sso_provider_id: true, + } + + + const setup = async ({ + userId, + onOpen, + showPasswordOptions, + showAuthMgmtButtons, + showDisableMfaButton, + userDisabled, + onUpdateUser, + onDisableUser, + onEnableUser, + onDeleteUser, + onUpdateUserPassword, + onExpireUserPassword, + onManageUserTokens, + onDisableUserMfa, + index, + }: testProps): Promise => { + ( () => { + render( + + ); + }); + }; + + + /* + const setup: React.FC = ({ + userId, + onOpen, + showPasswordOptions, + showAuthMgmtButtons, + showDisableMfaButton, + userDisabled, + onUpdateUser, + onDisableUser, + onEnableUser, + onDeleteUser, + onUpdateUserPassword, + onExpireUserPassword, + onManageUserTokens, + onDisableUserMfa, + index, + }) => {{ + await act(async () => { + render( + { + setSelectedUserId(userId); + }} + showPasswordOptions={!user.sso_provider_id} + showAuthMgmtButtons={user.id !== "notTestId"} + showDisableMfaButton={false} + userDisabled={false} + onUpdateUser={() => noop()} + onDisableUser={() => noop()} + onEnableUser={() => noop()} + onDeleteUser={() => noop()} + onUpdateUserPassword={() => noop()} + onExpireUserPassword={() => noop()} + onManageUserTokens={() => noop()} + onDisableUserMfa={() => noop()} + index={98} + /> + ); + }); + }; + */ + + it('should display generate/revoke api tokens button', async () => { + + const queryClient = new QueryClient() + + render( + { + setSelectedUserId(userId); + }} + showPasswordOptions={!user.sso_provider_id} + showAuthMgmtButtons={user.id !== "notTestId"} + showDisableMfaButton={false} + userDisabled={false} + onUpdateUser={() => noop()} + onDisableUser={() => noop()} + onEnableUser={() => noop()} + onDeleteUser={() => noop()} + onUpdateUserPassword={() => noop()} + onExpireUserPassword={() => noop()} + onManageUserTokens={() => noop()} + onDisableUserMfa={() => noop()} + index={98} + />, {queryClient} + ) + const apiKeyManagementButton = screen.findByRole('menuitem', { name: /generate \/ revoke api tokens/i }); + expect(apiKeyManagementButton).toBeInTheDocument(); + + },); + + it('should not display generate/revoke api tokens button', async () => { + + server.use( + rest.get(`/api/v2/config`, async (_req, res, ctx) => { + return res( + ctx.json(CONFIG_DISABLED_RESPONSE) + ); + }), + ); + + const queryClient = new QueryClient() + render( + { + setSelectedUserId(userId); + }} + showPasswordOptions={!user.sso_provider_id} + showAuthMgmtButtons={user.id !== "notTestId"} + showDisableMfaButton={false} + userDisabled={false} + onUpdateUser={() => noop()} + onDisableUser={() => noop()} + onEnableUser={() => noop()} + onDeleteUser={() => noop()} + onUpdateUserPassword={() => noop()} + onExpireUserPassword={() => noop()} + onManageUserTokens={() => noop()} + onDisableUserMfa={() => noop()} + index={98} + />, {queryClient} + ) + + await queryClient.invalidateQueries(configurationKeys.all) + const apiKeyManagementButton = screen.queryByRole('menuitem', { name: /generate \/ revoke api tokens/i }); + expect(apiKeyManagementButton).not.toBeInTheDocument(); + }); +}); diff --git a/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.tsx b/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.tsx index a4e4d15d59f..698219dad27 100644 --- a/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.tsx +++ b/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.tsx @@ -89,6 +89,7 @@ const UserActionsMenu: React.FC = ({ const [anchorEl, setAnchorEl] = React.useState(null); const apiTokensEnabled = useAPITokensConfiguration() + /* Event Handlers */ const handleOnOpen: React.MouseEventHandler = (event) => { From 01b980c0017b918f954d79d3ae7e58f88a44ed5f Mon Sep 17 00:00:00 2001 From: Nando Pena Date: Tue, 24 Feb 2026 16:20:20 -0600 Subject: [PATCH 6/9] Adjusted ui tests. Some cleanup --- .../bh-shared-ui/src/utils/object.ts | 2 + .../views/UserProfile/UserProfile.test.tsx | 33 +-- .../src/views/UserProfile/UserProfile.tsx | 4 +- .../src/views/Users/UserActionsMenu.test.tsx | 250 +++++------------- .../src/views/Users/UserActionsMenu.tsx | 2 +- 5 files changed, 87 insertions(+), 204 deletions(-) diff --git a/packages/javascript/bh-shared-ui/src/utils/object.ts b/packages/javascript/bh-shared-ui/src/utils/object.ts index 1ebfc4a70be..a1295129aa3 100644 --- a/packages/javascript/bh-shared-ui/src/utils/object.ts +++ b/packages/javascript/bh-shared-ui/src/utils/object.ts @@ -16,3 +16,5 @@ /** Returns Object.entries with results retaining their types */ export const typedEntries = (obj: T): [keyof T, T[keyof T]][] => Object.entries(obj) as any; + +export const noop = () => undefined; diff --git a/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx b/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx index 5180cd5838e..a9d3542cbd8 100644 --- a/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx +++ b/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx @@ -19,11 +19,11 @@ import { render, screen, waitFor, within } from '../../test-utils'; import UserProfile from './UserProfile'; +import { ConfigurationKey } from 'js-client-library'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { ConfigurationKey } from "js-client-library"; -import { QueryClient } from "react-query"; -import { configurationKeys } from "../../hooks"; +import { QueryClient } from 'react-query'; +import { configurationKeys } from '../../hooks'; const CONFIG_ENABLED_RESPONSE = { data: [ @@ -34,7 +34,7 @@ const CONFIG_ENABLED_RESPONSE = { }, }, ], -} +}; const CONFIG_DISABLED_RESPONSE = { data: [ @@ -45,17 +45,15 @@ const CONFIG_DISABLED_RESPONSE = { }, }, ], -} +}; const server = setupServer( rest.get(`/api/v2/self`, (req, res) => { return res(); }), rest.get(`/api/v2/config`, async (_req, res, ctx) => { - return res( - ctx.json(CONFIG_ENABLED_RESPONSE) - ); - }), + return res(ctx.json(CONFIG_ENABLED_RESPONSE)); + }) ); beforeAll(() => server.listen()); @@ -199,27 +197,22 @@ describe('UserProfile', () => { describe('Api Keys', () => { it('should display api key management button', async () => { - - render() + render(); const apiKeyManagementButton = await screen.findByRole('button', { name: 'API Key Management' }); expect(apiKeyManagementButton).toBeInTheDocument(); - }); it('should not display api key management button', async () => { - server.use( rest.get(`/api/v2/config`, async (_req, res, ctx) => { - return res( - ctx.json(CONFIG_DISABLED_RESPONSE) - ); - }), + return res(ctx.json(CONFIG_DISABLED_RESPONSE)); + }) ); - const queryClient = new QueryClient() - render(, {queryClient}) + const queryClient = new QueryClient(); + render(, { queryClient }); - await queryClient.invalidateQueries(configurationKeys.all) + await queryClient.invalidateQueries(configurationKeys.all); const apiKeyManagementButton = screen.queryByRole('button', { name: 'API Key Management' }); expect(apiKeyManagementButton).not.toBeInTheDocument(); }); diff --git a/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.tsx b/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.tsx index fb57e93d7c7..f1fff18da5b 100644 --- a/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.tsx +++ b/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.tsx @@ -28,10 +28,10 @@ import { TextWithFallback, UserTokenManagementDialog, } from '../../components'; +import { useAPITokensConfiguration } from '../../hooks'; import { useSelf } from '../../hooks/useBloodHoundUsers'; import { useNotifications } from '../../providers'; import { apiClient, getUsername } from '../../utils'; -import { useAPITokensConfiguration } from '../../hooks'; const UserProfile = () => { const { addNotification } = useNotifications(); @@ -46,7 +46,7 @@ const UserProfile = () => { const [disable2FASecret, setDisable2FASecret] = useState(''); const getSelfQuery = useSelf(); - const apiTokensEnabled = useAPITokensConfiguration() + const apiTokensEnabled = useAPITokensConfiguration(); const updateUserPasswordMutation = useMutation( ({ userId, ...payload }: { userId: string } & PutUserAuthSecretRequest) => diff --git a/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx b/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx index ed0bf1fcef2..904383a9553 100644 --- a/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx +++ b/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx @@ -1,11 +1,10 @@ -import {ConfigurationKey} from "js-client-library"; -import {setupServer} from "msw/node"; -import {rest} from "msw"; -import {render, screen} from "../../test-utils"; -import {QueryClient} from "react-query"; -import {configurationKeys} from "../../hooks"; -import UserActionsMenu from "./UserActionsMenu"; - +import userEvent from '@testing-library/user-event'; +import { ConfigurationKey } from 'js-client-library'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { render, screen } from '../../test-utils'; +import { noop } from '../../utils'; +import UserActionsMenu from './UserActionsMenu'; const CONFIG_ENABLED_RESPONSE = { data: [ @@ -16,7 +15,7 @@ const CONFIG_ENABLED_RESPONSE = { }, }, ], -} +}; const CONFIG_DISABLED_RESPONSE = { data: [ @@ -27,35 +26,14 @@ const CONFIG_DISABLED_RESPONSE = { }, }, ], -} - -const noop = () => undefined; - -interface testProps { - userId: string; - onOpen: (e: any, userId: string) => any; - showPasswordOptions: boolean; - showAuthMgmtButtons: boolean; - showDisableMfaButton: boolean; - userDisabled: boolean; - onUpdateUser: (e: any) => any; - onDisableUser: (e: any) => any; - onEnableUser: (e: any) => any; - onDeleteUser: (e: any) => any; - onUpdateUserPassword: (e: any) => any; - onExpireUserPassword: (e: any) => any; - onManageUserTokens: (e: any) => any; - onDisableUserMfa: (e: any) => any; - index: number; -} +}; +type ComponentProps = React.ComponentProps; const server = setupServer( rest.get(`/api/v2/config`, async (_req, res, ctx) => { - return res( - ctx.json(CONFIG_ENABLED_RESPONSE) - ); - }), + return res(ctx.json(CONFIG_ENABLED_RESPONSE)); + }) ); beforeAll(() => server.listen()); @@ -63,166 +41,76 @@ afterEach(() => server.resetHandlers()); afterAll(() => server.close()); describe('Api Keys', () => { + //const user = { + // id: 'testID', + // sso_provider_id: true, + //}; - const setSelectedUserId = (id: string) => { - return noop(); - } - - const user = { - id: "testID", - sso_provider_id: true, - } - - - const setup = async ({ - userId, - onOpen, - showPasswordOptions, - showAuthMgmtButtons, - showDisableMfaButton, - userDisabled, - onUpdateUser, - onDisableUser, - onEnableUser, - onDeleteUser, - onUpdateUserPassword, - onExpireUserPassword, - onManageUserTokens, - onDisableUserMfa, - index, - }: testProps): Promise => { - ( () => { - render( - - ); - }); - }; - + const setup = ( + { + userId = '', + onOpen = noop, + showPasswordOptions = false, + showAuthMgmtButtons = false, + showDisableMfaButton = false, + userDisabled = false, + onUpdateUser = noop, + onDisableUser = noop, + onEnableUser = noop, + onDeleteUser = noop, + onUpdateUserPassword = noop, + onExpireUserPassword = noop, + onManageUserTokens = noop, + onDisableUserMfa = noop, + index = 0, + } = {} as ComponentProps + ) => { + const user = userEvent.setup(); + + const screen = render( + + ); - /* - const setup: React.FC = ({ - userId, - onOpen, - showPasswordOptions, - showAuthMgmtButtons, - showDisableMfaButton, - userDisabled, - onUpdateUser, - onDisableUser, - onEnableUser, - onDeleteUser, - onUpdateUserPassword, - onExpireUserPassword, - onManageUserTokens, - onDisableUserMfa, - index, - }) => {{ - await act(async () => { - render( - { - setSelectedUserId(userId); - }} - showPasswordOptions={!user.sso_provider_id} - showAuthMgmtButtons={user.id !== "notTestId"} - showDisableMfaButton={false} - userDisabled={false} - onUpdateUser={() => noop()} - onDisableUser={() => noop()} - onEnableUser={() => noop()} - onDeleteUser={() => noop()} - onUpdateUserPassword={() => noop()} - onExpireUserPassword={() => noop()} - onManageUserTokens={() => noop()} - onDisableUserMfa={() => noop()} - index={98} - /> - ); - }); + return { screen, user }; }; - */ it('should display generate/revoke api tokens button', async () => { + const { user } = setup(); - const queryClient = new QueryClient() - - render( - { - setSelectedUserId(userId); - }} - showPasswordOptions={!user.sso_provider_id} - showAuthMgmtButtons={user.id !== "notTestId"} - showDisableMfaButton={false} - userDisabled={false} - onUpdateUser={() => noop()} - onDisableUser={() => noop()} - onEnableUser={() => noop()} - onDeleteUser={() => noop()} - onUpdateUserPassword={() => noop()} - onExpireUserPassword={() => noop()} - onManageUserTokens={() => noop()} - onDisableUserMfa={() => noop()} - index={98} - />, {queryClient} - ) - const apiKeyManagementButton = screen.findByRole('menuitem', { name: /generate \/ revoke api tokens/i }); - expect(apiKeyManagementButton).toBeInTheDocument(); + const button = screen.getByRole('button', { name: /show user actions/i }); - },); + await user.click(button); + //screen.logTestingPlaygroundURL() + await screen.findByRole('menuitem', { name: /generate \/ revoke api tokens/i }); + }); it('should not display generate/revoke api tokens button', async () => { - server.use( rest.get(`/api/v2/config`, async (_req, res, ctx) => { - return res( - ctx.json(CONFIG_DISABLED_RESPONSE) - ); - }), + return res(ctx.json(CONFIG_DISABLED_RESPONSE)); + }) ); + const { user } = setup(); - const queryClient = new QueryClient() - render( - { - setSelectedUserId(userId); - }} - showPasswordOptions={!user.sso_provider_id} - showAuthMgmtButtons={user.id !== "notTestId"} - showDisableMfaButton={false} - userDisabled={false} - onUpdateUser={() => noop()} - onDisableUser={() => noop()} - onEnableUser={() => noop()} - onDeleteUser={() => noop()} - onUpdateUserPassword={() => noop()} - onExpireUserPassword={() => noop()} - onManageUserTokens={() => noop()} - onDisableUserMfa={() => noop()} - index={98} - />, {queryClient} - ) - - await queryClient.invalidateQueries(configurationKeys.all) + const button = screen.getByRole('button', { name: /show user actions/i }); + await user.click(button); + //screen.logTestingPlaygroundURL() const apiKeyManagementButton = screen.queryByRole('menuitem', { name: /generate \/ revoke api tokens/i }); expect(apiKeyManagementButton).not.toBeInTheDocument(); }); diff --git a/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.tsx b/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.tsx index 698219dad27..d59b6a08c0f 100644 --- a/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.tsx +++ b/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.tsx @@ -88,7 +88,7 @@ const UserActionsMenu: React.FC = ({ /* Hooks */ const [anchorEl, setAnchorEl] = React.useState(null); - const apiTokensEnabled = useAPITokensConfiguration() + const apiTokensEnabled = useAPITokensConfiguration(); /* Event Handlers */ From b12550412a0526c80eb13ee06e953b62a9efd628 Mon Sep 17 00:00:00 2001 From: Ibra Date: Wed, 25 Feb 2026 11:27:20 -0600 Subject: [PATCH 7/9] Cleanup --- .../src/views/Users/UserActionsMenu.test.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx b/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx index 904383a9553..587c19c6061 100644 --- a/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx +++ b/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx @@ -41,10 +41,6 @@ afterEach(() => server.resetHandlers()); afterAll(() => server.close()); describe('Api Keys', () => { - //const user = { - // id: 'testID', - // sso_provider_id: true, - //}; const setup = ( { @@ -94,9 +90,8 @@ describe('Api Keys', () => { const { user } = setup(); const button = screen.getByRole('button', { name: /show user actions/i }); - await user.click(button); - //screen.logTestingPlaygroundURL() + await screen.findByRole('menuitem', { name: /generate \/ revoke api tokens/i }); }); @@ -110,7 +105,7 @@ describe('Api Keys', () => { const button = screen.getByRole('button', { name: /show user actions/i }); await user.click(button); - //screen.logTestingPlaygroundURL() + const apiKeyManagementButton = screen.queryByRole('menuitem', { name: /generate \/ revoke api tokens/i }); expect(apiKeyManagementButton).not.toBeInTheDocument(); }); From 8c1d6ec1f63d3856729f7bf024f64c306eaa3235 Mon Sep 17 00:00:00 2001 From: Ibra Date: Wed, 25 Feb 2026 11:43:14 -0600 Subject: [PATCH 8/9] License header --- .../src/views/Users/UserActionsMenu.test.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx b/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx index 587c19c6061..bbb7bf86cc0 100644 --- a/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx +++ b/packages/javascript/bh-shared-ui/src/views/Users/UserActionsMenu.test.tsx @@ -1,3 +1,18 @@ +// Copyright 2026 Specter Ops, Inc. +// +// Licensed under the Apache License, Version 2.0 +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 import userEvent from '@testing-library/user-event'; import { ConfigurationKey } from 'js-client-library'; import { rest } from 'msw'; @@ -41,7 +56,6 @@ afterEach(() => server.resetHandlers()); afterAll(() => server.close()); describe('Api Keys', () => { - const setup = ( { userId = '', From cf7a7423bd7452c02f63a7e368a9b36c4631272b Mon Sep 17 00:00:00 2001 From: Ibra Date: Thu, 26 Feb 2026 11:45:27 -0600 Subject: [PATCH 9/9] Added await and waitFor() on functions not returning a promise --- .../src/views/UserProfile/UserProfile.test.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx b/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx index a9d3542cbd8..575c956016c 100644 --- a/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx +++ b/packages/javascript/bh-shared-ui/src/views/UserProfile/UserProfile.test.tsx @@ -212,8 +212,11 @@ describe('Api Keys', () => { const queryClient = new QueryClient(); render(, { queryClient }); - await queryClient.invalidateQueries(configurationKeys.all); - const apiKeyManagementButton = screen.queryByRole('button', { name: 'API Key Management' }); - expect(apiKeyManagementButton).not.toBeInTheDocument(); + await waitFor(() => + expect(queryClient.getQueryState(configurationKeys.all)?.status).toBe('success') + ); + await waitFor(() => + expect(screen.queryByRole('button', { name: 'API Key Management' })).not.toBeInTheDocument() + ); }); });