Skip to content

Commit 5933435

Browse files
committed
fix: access denied for operations
1 parent 00d0286 commit 5933435

File tree

4 files changed

+68
-5
lines changed

4 files changed

+68
-5
lines changed

src/containers/Operations/Operations.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,17 @@ export function Operations({database, scrollContainerRef}: OperationsProps) {
3434
scrollContainerRef,
3535
});
3636

37-
if (isAccessError(error)) {
38-
return <AccessDenied position="left" />;
39-
}
40-
4137
const settings = React.useMemo(() => {
4238
return {
4339
...DEFAULT_TABLE_SETTINGS,
4440
sortable: false,
4541
};
4642
}, []);
4743

44+
if (isAccessError(error)) {
45+
return <AccessDenied position="left" />;
46+
}
47+
4848
return (
4949
<TableWithControlsLayout>
5050
<TableWithControlsLayout.Controls>

tests/suites/tenant/diagnostics/tabs/OperationsModel.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export class OperationsTable extends BaseModel {
1313
private emptyState: Locator;
1414
private loadingMore: Locator;
1515
private scrollContainer: Locator;
16+
private accessDeniedState: Locator;
17+
private accessDeniedTitle: Locator;
1618

1719
constructor(page: Page) {
1820
super(page, page.locator('.kv-tenant-diagnostics'));
@@ -22,6 +24,9 @@ export class OperationsTable extends BaseModel {
2224
this.emptyState = page.locator('.operations__table:has-text("No operations data")');
2325
this.loadingMore = page.locator('.operations__loading-more');
2426
this.scrollContainer = page.locator('.kv-tenant-diagnostics__page-wrapper');
27+
// AccessDenied component is rendered at the root level of Operations component
28+
this.accessDeniedState = page.locator('.kv-tenant-diagnostics .empty-state');
29+
this.accessDeniedTitle = this.accessDeniedState.locator('.empty-state__title');
2530
}
2631

2732
async waitForTableVisible() {
@@ -124,4 +129,17 @@ export class OperationsTable extends BaseModel {
124129

125130
return false;
126131
}
132+
133+
async isAccessDeniedVisible(): Promise<boolean> {
134+
try {
135+
await this.accessDeniedState.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
136+
return true;
137+
} catch {
138+
return false;
139+
}
140+
}
141+
142+
async getAccessDeniedTitle(): Promise<string> {
143+
return await this.accessDeniedTitle.innerText();
144+
}
127145
}

tests/suites/tenant/diagnostics/tabs/operations.test.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import {tenantName} from '../../../../utils/constants';
44
import {TenantPage} from '../../TenantPage';
55
import {Diagnostics, DiagnosticsTab} from '../Diagnostics';
66

7-
import {setupEmptyOperationsMock, setupOperationsMock} from './operationsMocks';
7+
import {
8+
setupEmptyOperationsMock,
9+
setupOperation403Mock,
10+
setupOperationsMock,
11+
} from './operationsMocks';
812

913
test.describe('Operations Tab - Infinite Query', () => {
1014
test('loads initial page of operations on tab click', async ({page}) => {
@@ -119,4 +123,31 @@ test.describe('Operations Tab - Infinite Query', () => {
119123
const rowCount = await diagnostics.operations.getRowCount();
120124
expect(rowCount).toBeLessThanOrEqual(1);
121125
});
126+
127+
test('shows access denied when operations request returns 403', async ({page}) => {
128+
// Setup 403 error mock
129+
await setupOperation403Mock(page);
130+
131+
const pageQueryParams = {
132+
schema: tenantName,
133+
database: tenantName,
134+
tenantPage: 'diagnostics',
135+
};
136+
137+
const tenantPageInstance = new TenantPage(page);
138+
await tenantPageInstance.goto(pageQueryParams);
139+
140+
const diagnostics = new Diagnostics(page);
141+
await diagnostics.clickTab(DiagnosticsTab.Operations);
142+
// Wait a bit for potential loading
143+
await page.waitForTimeout(2000);
144+
145+
// Wait for access denied state to be visible
146+
const isAccessDeniedVisible = await diagnostics.operations.isAccessDeniedVisible();
147+
expect(isAccessDeniedVisible).toBe(true);
148+
149+
// Verify the access denied message
150+
const accessDeniedTitle = await diagnostics.operations.getAccessDeniedTitle();
151+
expect(accessDeniedTitle).toBe('Access denied');
152+
});
122153
});

tests/suites/tenant/diagnostics/tabs/operationsMocks.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,20 @@ export const setupOperationErrorMock = async (page: Page) => {
226226
});
227227
};
228228

229+
export const setupOperation403Mock = async (page: Page) => {
230+
await page.route(`${backend}/operation/list*`, async (route) => {
231+
await new Promise((resolve) => setTimeout(resolve, MOCK_DELAY));
232+
233+
await route.fulfill({
234+
status: 403,
235+
contentType: 'application/json',
236+
body: JSON.stringify({
237+
error: 'Forbidden',
238+
}),
239+
});
240+
});
241+
};
242+
229243
// Helper to setup all required mocks for operations
230244
export const setupAllOperationMocks = async (page: Page, options?: {totalOperations?: number}) => {
231245
await setupOperationsMock(page, options);

0 commit comments

Comments
 (0)