Skip to content

Commit 59fccb3

Browse files
committed
fix integration tests
1 parent c09f648 commit 59fccb3

11 files changed

Lines changed: 122 additions & 172 deletions

frontend/src/components/pages/observability/observability-page.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ vi.mock('config', async (importOriginal) => {
5454
});
5555

5656
vi.mock('state/ui-state', () => ({
57+
setPageHeader: vi.fn(),
5758
uiState: {
5859
pageTitle: '',
5960
pageBreadcrumbs: [],

frontend/src/components/pages/security/acls/acl-detail-page.test.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
*/
1111

1212
import { render, screen, waitFor } from '@testing-library/react';
13-
import userEvent from '@testing-library/user-event';
1413
import { beforeEach, describe, expect, test, vi } from 'vitest';
1514

1615
import AclDetailPage from './acl-detail-page';
@@ -47,6 +46,7 @@ vi.mock('@tanstack/react-router', async (importOriginal) => {
4746
});
4847

4948
vi.mock('state/ui-state', () => ({
49+
setPageHeader: vi.fn(),
5050
uiState: { pageBreadcrumbs: [] },
5151
}));
5252

@@ -124,13 +124,6 @@ describe('AclDetailPage — principal URL encoding', () => {
124124
render(<AclDetailPage />);
125125

126126
const editButton = await screen.findByTestId('update-acl-button');
127-
await userEvent.click(editButton);
128-
129-
await waitFor(() => {
130-
expect(mockNavigate).toHaveBeenCalledWith({
131-
to: '/security/acls/Group:mygroup/update',
132-
search: { host: '*' },
133-
});
134-
});
127+
expect(editButton).toHaveAttribute('href', '/security/acls/Group:mygroup/update?host=*');
135128
});
136129
});

frontend/src/components/pages/security/acls/acl-detail-page.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { useLayoutEffect } from 'react';
1818
import { HostSelector } from './host-selector';
1919
import { useGetAclsByPrincipal } from '../../../../react-query/api/acl';
2020
import { setPageHeader } from '../../../../state/ui-state';
21+
import { Button } from '../../../redpanda-ui/components/button';
2122
import { Text } from '../../../redpanda-ui/components/typography';
2223
import { ACLDetails } from '../shared/acl-details';
2324
import { parsePrincipalFromParam } from '../shared/principal-utils';
@@ -52,10 +53,17 @@ const AclDetailPage = () => {
5253
return <HostSelector baseUrl={`/security/acls/${aclName}/details`} hosts={data} principalName={principalName} />;
5354
}
5455

56+
const editHref = `/security/acls/${aclName}/update${host ? `?host=${host}` : ''}`;
57+
5558
return (
5659
<div className="flex flex-col gap-4">
5760
<h2 className="pt-4 pb-3 font-semibold text-xl">ACL: {principalName}</h2>
5861
<Text>Configuration details</Text>
62+
<Button asChild variant="outline">
63+
<a data-testid="update-acl-button" href={editHref}>
64+
Edit
65+
</a>
66+
</Button>
5967
<ACLDetails isSimpleView={false} rules={acls.rules} sharedConfig={acls.sharedConfig} />
6068
</div>
6169
);

frontend/src/components/pages/security/roles/role-create-dialog.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*/
1111

1212
import { create } from '@bufbuild/protobuf';
13+
import { ConnectError } from '@connectrpc/connect';
1314
import { useNavigate } from '@tanstack/react-router';
1415
import { CreateRoleRequestSchema } from 'protogen/redpanda/api/dataplane/v1/security_pb';
1516
import { useState } from 'react';

frontend/src/components/pages/security/tabs/permissions-list-tab.test.tsx

Lines changed: 63 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import { render, screen, within } from '@testing-library/react';
1313
import userEvent from '@testing-library/user-event';
14+
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
1415
import type { ReactNode } from 'react';
1516
import { beforeEach, describe, expect, test, vi } from 'vitest';
1617

@@ -23,104 +24,7 @@ import { beforeEach, describe, expect, test, vi } from 'vitest';
2324
* (they're ACL-only principals, not SASL-SCRAM accounts)
2425
*/
2526

26-
const { listACLsData } = vi.hoisted(() => ({
27-
listACLsData: {
28-
// Return a SCRAM user, a non-SCRAM user (ACL-only), and a Group principal
29-
data: [
30-
{ host: '*', principal: 'User:scram-admin', principalType: 'User', principalName: 'scram-admin', hasAcl: true },
31-
{
32-
host: '*',
33-
principal: 'User:acl-only-user',
34-
principalType: 'User',
35-
principalName: 'acl-only-user',
36-
hasAcl: true,
37-
},
38-
{ host: '*', principal: 'Group:engineering', principalType: 'Group', principalName: 'engineering', hasAcl: true },
39-
],
40-
error: null,
41-
isError: false,
42-
isLoading: false,
43-
},
44-
}));
45-
46-
vi.mock('@redpanda-data/ui', () => {
47-
const Div = ({ children, ...props }: { children?: ReactNode; [key: string]: unknown }) => (
48-
<div {...props}>{children}</div>
49-
);
50-
51-
return {
52-
Alert: Div,
53-
AlertDescription: Div,
54-
AlertIcon: () => <span />,
55-
AlertTitle: Div,
56-
Badge: Div,
57-
Box: Div,
58-
Button: ({
59-
children,
60-
isDisabled,
61-
onClick,
62-
...props
63-
}: {
64-
children?: ReactNode;
65-
isDisabled?: boolean;
66-
onClick?: () => void;
67-
[key: string]: unknown;
68-
}) => (
69-
<button disabled={isDisabled} onClick={onClick} {...props}>
70-
{children}
71-
</button>
72-
),
73-
createStandaloneToast: () => ({
74-
ToastContainer: () => null,
75-
toast: vi.fn(),
76-
}),
77-
DataTable: ({
78-
columns,
79-
data,
80-
emptyText,
81-
}: {
82-
columns: Array<{
83-
cell?: (ctx: { row: { original: Record<string, unknown> } }) => ReactNode;
84-
header?: ReactNode;
85-
id?: string;
86-
}>;
87-
data: Record<string, unknown>[];
88-
emptyText?: ReactNode;
89-
}) =>
90-
data.length > 0 ? (
91-
<table>
92-
<tbody>
93-
{data.map((row, rowIndex) => (
94-
<tr data-testid={`row-${row.name ?? rowIndex}`} key={String(row.name ?? rowIndex)}>
95-
{columns.map((column, colIndex) => (
96-
<td key={column.id ?? colIndex}>{column.cell?.({ row: { original: row } }) ?? null}</td>
97-
))}
98-
</tr>
99-
))}
100-
</tbody>
101-
</table>
102-
) : (
103-
<div>{emptyText}</div>
104-
),
105-
Flex: Div,
106-
SearchField: ({
107-
placeholderText,
108-
searchText,
109-
setSearchText,
110-
}: {
111-
placeholderText?: string;
112-
searchText?: string;
113-
setSearchText?: (value: string) => void;
114-
}) => (
115-
<input onChange={(e) => setSearchText?.(e.target.value)} placeholder={placeholderText} value={searchText ?? ''} />
116-
),
117-
Skeleton: Div,
118-
Text: Div,
119-
Tooltip: ({ children }: { children?: ReactNode }) => <>{children}</>,
120-
redpandaTheme: {},
121-
redpandaToastOptions: { defaultOptions: {} },
122-
};
123-
});
27+
const NuqsWrapper = ({ children }: { children: ReactNode }) => <NuqsTestingAdapter>{children}</NuqsTestingAdapter>;
12428

12529
vi.mock('@tanstack/react-router', async (importOriginal) => {
12630
const actual = await importOriginal<typeof import('@tanstack/react-router')>();
@@ -135,6 +39,10 @@ vi.mock('@tanstack/react-router', async (importOriginal) => {
13539
};
13640
});
13741

42+
vi.mock('../shared/security-tabs-nav', () => ({
43+
SecurityTabsNav: () => null,
44+
}));
45+
13846
vi.mock('../shared/delete-user-confirm-modal', () => ({
13947
DeleteUserConfirmModal: ({
14048
open,
@@ -155,10 +63,6 @@ vi.mock('../shared/delete-user-confirm-modal', () => ({
15563
) : null,
15664
}));
15765

158-
vi.mock('../shared/user-role-tags', () => ({
159-
UserRoleTags: () => null,
160-
}));
161-
16266
vi.mock('../../../../components/misc/error-result', () => ({
16367
default: () => null,
16468
}));
@@ -209,27 +113,60 @@ vi.mock('../../../../state/rest-interfaces', () => ({
209113
AclRequestDefault: {},
210114
}));
211115

212-
vi.mock('../../../misc/section', () => ({
213-
default: ({ children }: { children?: ReactNode }) => <section>{children}</section>,
214-
}));
215-
216-
vi.mock('react-query/api/cluster-status', () => ({
217-
useGetRedpandaInfoQuery: () => ({ data: {}, isSuccess: true }),
116+
vi.mock('../../../../react-query/api/acl', () => ({
117+
useCreateACLMutation: () => ({ mutateAsync: vi.fn() }),
118+
useDeleteAclMutation: () => ({ mutateAsync: vi.fn() }),
218119
}));
219120

220-
vi.mock('react-query/api/user', () => ({
121+
vi.mock('../../../../react-query/api/user', () => ({
221122
useInvalidateUsersCache: () => vi.fn(),
222123
useDeleteUserMutation: () => ({ mutateAsync: vi.fn().mockResolvedValue(undefined) }),
223-
// "scram-admin" is a SCRAM user; "acl-only-user" is NOT (only has ACLs)
224-
useListUsersQuery: () => ({
225-
data: { users: [{ name: 'scram-admin' }] },
226-
isLoading: false,
227-
}),
124+
useListUsersQuery: () => ({ data: { users: [] }, isLoading: false }),
228125
}));
229126

230-
vi.mock('react-query/api/acl', () => ({
231-
useDeleteAclMutation: () => ({ mutateAsync: vi.fn() }),
232-
useListACLAsPrincipalGroups: () => listACLsData,
127+
vi.mock('../hooks/use-principal-permissions', () => ({
128+
usePrincipalPermissions: () => ({
129+
principalGroups: [
130+
{
131+
principal: 'User:scram-admin',
132+
principalType: 'User',
133+
principalName: 'scram-admin',
134+
isScramUser: true,
135+
directAcls: [],
136+
roleAclGroups: [],
137+
directAclCount: 0,
138+
inheritedAclCount: 0,
139+
denyCount: 0,
140+
},
141+
{
142+
principal: 'User:acl-only-user',
143+
principalType: 'User',
144+
principalName: 'acl-only-user',
145+
isScramUser: false,
146+
directAcls: [],
147+
roleAclGroups: [],
148+
directAclCount: 0,
149+
inheritedAclCount: 0,
150+
denyCount: 0,
151+
},
152+
{
153+
principal: 'Group:engineering',
154+
principalType: 'Group',
155+
principalName: 'engineering',
156+
isScramUser: false,
157+
directAcls: [],
158+
roleAclGroups: [],
159+
directAclCount: 0,
160+
inheritedAclCount: 0,
161+
denyCount: 0,
162+
},
163+
],
164+
isAclsLoading: false,
165+
isAclsError: false,
166+
aclsError: null,
167+
isUsersError: false,
168+
usersError: null,
169+
}),
233170
}));
234171

235172
import { PermissionsListTab } from './permissions-list-tab';
@@ -242,11 +179,11 @@ describe('Permissions List - delete dropdown for different principal types', ()
242179
test('Group principal does not show "Delete User" options in dropdown', async () => {
243180
const user = userEvent.setup();
244181

245-
render(<PermissionsListTab />);
182+
render(<PermissionsListTab />, { wrapper: NuqsWrapper });
246183

247184
const groupRow = await screen.findByTestId('row-engineering');
248-
const deleteButton = within(groupRow).getByRole('button');
249-
await user.click(deleteButton);
185+
const actionsDiv = within(groupRow).getByTestId('actions-engineering');
186+
await user.click(within(actionsDiv).getByRole('button'));
250187

251188
// Group should only have "Delete (ACLs only)", not user-delete options
252189
expect(screen.queryByText('Delete (User and ACLs)')).not.toBeInTheDocument();
@@ -257,12 +194,11 @@ describe('Permissions List - delete dropdown for different principal types', ()
257194
test('SCRAM user principal has "Delete User" options enabled', async () => {
258195
const user = userEvent.setup();
259196

260-
// "scram-admin" exists in usersData.users — it's a real SCRAM user
261-
render(<PermissionsListTab />);
197+
render(<PermissionsListTab />, { wrapper: NuqsWrapper });
262198

263199
const scramRow = await screen.findByTestId('row-scram-admin');
264-
const deleteButton = within(scramRow).getByRole('button');
265-
await user.click(deleteButton);
200+
const actionsDiv = within(scramRow).getByTestId('actions-scram-admin');
201+
await user.click(within(actionsDiv).getByRole('button'));
266202

267203
// SCRAM user should have all delete options available and enabled
268204
const deleteUserAndAcls = screen.getByText('Delete (User and ACLs)');
@@ -277,13 +213,12 @@ describe('Permissions List - delete dropdown for different principal types', ()
277213
test('Group principal has "Delete (ACLs only)" available', async () => {
278214
const user = userEvent.setup();
279215

280-
render(<PermissionsListTab />);
216+
render(<PermissionsListTab />, { wrapper: NuqsWrapper });
281217

282218
const groupRow = await screen.findByTestId('row-engineering');
283-
const deleteButton = within(groupRow).getByRole('button');
284-
await user.click(deleteButton);
219+
const actionsDiv = within(groupRow).getByTestId('actions-engineering');
220+
await user.click(within(actionsDiv).getByRole('button'));
285221

286-
// Even though user-delete options are hidden, "Delete (ACLs only)" is always available
287222
expect(screen.getByText('Delete (ACLs only)')).toBeInTheDocument();
288223
});
289224
});

frontend/src/components/pages/security/tabs/permissions-list-tab.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ const PrincipalRow: FC<PrincipalRowProps> = ({ group, isExpanded, onToggle, onDe
126126
userName={group.principalName}
127127
/>
128128

129-
<div className="border-b">
129+
<div className="border-b" data-testid={`row-${group.principalName}`}>
130130
{/* Principal header row */}
131131
<div
132132
className="flex cursor-pointer items-center gap-2 px-3 py-3 hover:bg-muted/20"
@@ -153,6 +153,7 @@ const PrincipalRow: FC<PrincipalRowProps> = ({ group, isExpanded, onToggle, onDe
153153

154154
<div
155155
className="flex items-center gap-1"
156+
data-testid={`actions-${group.principalName}`}
156157
onClick={(e) => e.stopPropagation()}
157158
onKeyDown={(e) => e.stopPropagation()}
158159
role="presentation"
@@ -368,7 +369,7 @@ export const PermissionsListTab: FC = () => {
368369
<div className="rounded-md border">
369370
{[0, 1, 2].map((i) => (
370371
<div className="flex items-center gap-3 border-b px-3 py-3 last:border-b-0" key={i}>
371-
<Skeleton className="h-4 w-4 shrink-0" variant="rectangular" />
372+
<Skeleton className="h-4 w-4 shrink-0" variant="rounded" />
372373
<Skeleton variant="text" width="md" />
373374
<Skeleton variant="text" width="sm" />
374375
</div>

0 commit comments

Comments
 (0)