From a532e63c108df815e9db28e8b4976d998079d13a Mon Sep 17 00:00:00 2001 From: Scanner Date: Sun, 19 Jul 2026 20:04:55 -0400 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=8C=B1=20fix:=20stabilize=20flaky=20c?= =?UTF-8?q?overage=20suite=20tests=20(shards=203/6/11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Scanner Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../__tests__/CreateNamespaceModal.test.tsx | 12 ++++- .../__tests__/NamespaceAccessPanel.test.tsx | 7 ++- .../rbac/__tests__/CanIChecker.test.tsx | 52 +++++++++++++++---- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/web/src/components/namespaces/__tests__/CreateNamespaceModal.test.tsx b/web/src/components/namespaces/__tests__/CreateNamespaceModal.test.tsx index cd38b52805..63e6cc4cc1 100644 --- a/web/src/components/namespaces/__tests__/CreateNamespaceModal.test.tsx +++ b/web/src/components/namespaces/__tests__/CreateNamespaceModal.test.tsx @@ -63,7 +63,7 @@ describe('CreateNamespaceModal', () => { expect(screen.getAllByRole('combobox').length).toBeGreaterThan(0) }) - it('initializes with first cluster selected', () => { + it('initializes with cluster placeholder selected', () => { render( { const comboboxes = screen.getAllByRole('combobox') expect(comboboxes.length).toBeGreaterThan(0) - expect((comboboxes[0] as HTMLSelectElement).value).toBe('cluster-1') + expect((comboboxes[0] as HTMLSelectElement).value).toBe('') }) it('allows cluster selection change', async () => { @@ -124,8 +124,10 @@ describe('CreateNamespaceModal', () => { const nameInput = screen.getByPlaceholderText('my-namespace') const teamInput = screen.getByPlaceholderText('platform-team') + const clusterSelect = screen.getAllByRole('combobox')[0] const createBtn = screen.getByRole('button', { name: /create/i }) + await user.selectOptions(clusterSelect, 'cluster-1') await user.type(nameInput, 'test-ns') await user.type(teamInput, 'my-team') await user.click(createBtn) @@ -153,8 +155,10 @@ describe('CreateNamespaceModal', () => { ) const nameInput = screen.getByPlaceholderText('my-namespace') + const clusterSelect = screen.getAllByRole('combobox')[0] const createBtn = screen.getByRole('button', { name: /create/i }) + await user.selectOptions(clusterSelect, 'cluster-1') await user.type(nameInput, 'existing-ns') await user.click(createBtn) @@ -229,8 +233,10 @@ describe('CreateNamespaceModal', () => { const nameInput = screen.getByPlaceholderText('my-namespace') const teamInput = screen.getByPlaceholderText('platform-team') + const clusterSelect = screen.getAllByRole('combobox')[0] const createBtn = screen.getByRole('button', { name: /create/i }) + await user.selectOptions(clusterSelect, 'cluster-1') await user.type(nameInput, 'test-ns') await user.type(teamInput, 'platform-team') await user.click(createBtn) @@ -256,8 +262,10 @@ describe('CreateNamespaceModal', () => { ) const nameInput = screen.getByPlaceholderText('my-namespace') + const clusterSelect = screen.getAllByRole('combobox')[0] const createBtn = screen.getByRole('button', { name: /create/i }) + await user.selectOptions(clusterSelect, 'cluster-1') await user.type(nameInput, 'test-ns') await user.click(createBtn) diff --git a/web/src/components/namespaces/__tests__/NamespaceAccessPanel.test.tsx b/web/src/components/namespaces/__tests__/NamespaceAccessPanel.test.tsx index c93ae452cc..0f1f076a16 100644 --- a/web/src/components/namespaces/__tests__/NamespaceAccessPanel.test.tsx +++ b/web/src/components/namespaces/__tests__/NamespaceAccessPanel.test.tsx @@ -290,10 +290,9 @@ describe('NamespaceAccessPanel', () => { expect(screen.getByText('alice')).toBeInTheDocument() }) - // Verify first call with original namespace - expect(api.get).toHaveBeenCalledWith( - expect.stringContaining('test-namespace') - ) + // Verify first call with original namespace. api.get also receives an + // AbortSignal options object, so assert against the URL argument directly. + expect(vi.mocked(api.get).mock.calls[0]?.[0]).toContain('test-namespace') const newNamespace: NamespaceDetails = { ...mockNamespace, diff --git a/web/src/components/rbac/__tests__/CanIChecker.test.tsx b/web/src/components/rbac/__tests__/CanIChecker.test.tsx index 4dec88695e..d6c541647c 100644 --- a/web/src/components/rbac/__tests__/CanIChecker.test.tsx +++ b/web/src/components/rbac/__tests__/CanIChecker.test.tsx @@ -23,8 +23,11 @@ vi.mock('../../../hooks/usePermissions', () => ({ }), })) -let mockClusters = [{ name: 'cluster-a' }, { name: 'cluster-b' }] -let mockNamespaces = ['default', 'kube-system', 'kube-public'] +const defaultMockClusters = [{ name: 'cluster-a' }, { name: 'cluster-b' }] +const defaultMockNamespaces = ['default', 'kube-system', 'kube-public'] + +let mockClusters = [...defaultMockClusters] +let mockNamespaces = [...defaultMockNamespaces] vi.mock('../../../hooks/useMCP', () => ({ useClusters: () => ({ @@ -59,6 +62,20 @@ vi.mock('../../PageErrorBoundary', () => ({ /* ---------- Tests ---------- */ +function selectCluster(cluster = 'cluster-a') { + fireEvent.change(screen.getByTestId('can-i-cluster'), { target: { value: cluster } }) +} + +beforeEach(() => { + vi.clearAllMocks() + mockClusters = [...defaultMockClusters] + mockNamespaces = [...defaultMockNamespaces] + mockChecking = false + mockResult = null + mockError = null +}) + + describe('CanIChecker — Initial Rendering', () => { beforeEach(() => { vi.clearAllMocks() @@ -85,9 +102,10 @@ describe('CanIChecker — Initial Rendering', () => { const clusterSelect = screen.getByTestId('can-i-cluster') as HTMLSelectElement const options = Array.from(clusterSelect.options) - expect(options).toHaveLength(2) - expect(options[0].value).toBe('cluster-a') - expect(options[1].value).toBe('cluster-b') + expect(options).toHaveLength(3) + expect(options[0].value).toBe('') + expect(options[1].value).toBe('cluster-a') + expect(options[2].value).toBe('cluster-b') }) it('populates namespace dropdown with fetched namespaces', () => { @@ -102,11 +120,11 @@ describe('CanIChecker — Initial Rendering', () => { expect(options.some(opt => opt.value === 'kube-system')).toBe(true) }) - it('defaults to first cluster', () => { + it('starts with no cluster selected', () => { render() const clusterSelect = screen.getByTestId('can-i-cluster') as HTMLSelectElement - expect(clusterSelect.value).toBe('cluster-a') + expect(clusterSelect.value).toBe('') }) it('defaults verb and resource to common values', () => { @@ -130,6 +148,7 @@ describe('CanIChecker — Form Interactions', () => { it('calls checkPermission with defaults when Check button is clicked', async () => { render() + selectCluster() const checkBtn = screen.getByTestId('can-i-check') await userEvent.click(checkBtn) @@ -161,6 +180,7 @@ describe('CanIChecker — Form Interactions', () => { it('allows changing verb selection', async () => { render() + selectCluster() const verbSelect = screen.getByTestId('can-i-verb') fireEvent.change(verbSelect, { target: { value: 'list' } }) @@ -177,6 +197,7 @@ describe('CanIChecker — Form Interactions', () => { it('allows changing resource selection', async () => { render() + selectCluster() const resourceSelect = screen.getByTestId('can-i-resource') fireEvent.change(resourceSelect, { target: { value: 'deployments' } }) @@ -193,6 +214,7 @@ describe('CanIChecker — Form Interactions', () => { it('allows changing namespace selection', async () => { render() + selectCluster() const nsSelect = screen.getByTestId('can-i-namespace') fireEvent.change(nsSelect, { target: { value: 'kube-system' } }) @@ -209,6 +231,7 @@ describe('CanIChecker — Form Interactions', () => { it('sends undefined namespace when "all namespaces" is selected', async () => { render() + selectCluster() const nsSelect = screen.getByTestId('can-i-namespace') // First option is "all namespaces" @@ -235,6 +258,7 @@ describe('CanIChecker — Form Interactions', () => { it('uses custom verb value when submitted', async () => { render() + selectCluster() const verbSelect = screen.getByTestId('can-i-verb') fireEvent.change(verbSelect, { target: { value: 'custom' } }) @@ -263,6 +287,7 @@ describe('CanIChecker — Form Interactions', () => { it('uses custom resource value when submitted', async () => { render() + selectCluster() const resourceSelect = screen.getByTestId('can-i-resource') fireEvent.change(resourceSelect, { target: { value: 'custom' } }) @@ -291,6 +316,7 @@ describe('CanIChecker — Form Interactions', () => { it('uses custom API group value when submitted', async () => { render() + selectCluster() const apiGroupSelect = screen.getByTestId('can-i-api-group') fireEvent.change(apiGroupSelect, { target: { value: 'custom' } }) @@ -384,6 +410,7 @@ describe('CanIChecker — Result Display (Allowed)', () => { mockError = null render() + selectCluster() await userEvent.click(screen.getByTestId('can-i-check')) @@ -426,6 +453,7 @@ describe('CanIChecker — Result Display (Denied)', () => { mockError = null render() + selectCluster() await userEvent.click(screen.getByTestId('can-i-check')) @@ -480,10 +508,6 @@ describe('CanIChecker — No Clusters Available', () => { mockError = null }) - afterEach(() => { - // Restore default clusters - mockClusters = [{ name: 'cluster-a' }, { name: 'cluster-b' }] - }) it('shows warning and disables check button when no clusters available', () => { render() @@ -546,6 +570,7 @@ describe('CanIChecker — Edge Cases', () => { it('handles wildcard resources', async () => { render() + selectCluster() const resourceSelect = screen.getByTestId('can-i-resource') fireEvent.change(resourceSelect, { target: { value: 'custom' } }) @@ -577,6 +602,7 @@ describe('CanIChecker — Edge Cases', () => { it('handles special characters in custom inputs', async () => { render() + selectCluster() const resourceSelect = screen.getByTestId('can-i-resource') fireEvent.change(resourceSelect, { target: { value: 'custom' } }) @@ -596,6 +622,7 @@ describe('CanIChecker — Edge Cases', () => { it('handles multiple rapid clicks on check button', async () => { render() + selectCluster() const checkBtn = screen.getByTestId('can-i-check') @@ -618,6 +645,7 @@ describe('CanIChecker — API Group Handling', () => { it('sends correct API group for apps resources', async () => { render() + selectCluster() const resourceSelect = screen.getByTestId('can-i-resource') fireEvent.change(resourceSelect, { target: { value: 'deployments' } }) @@ -635,6 +663,7 @@ describe('CanIChecker — API Group Handling', () => { it('sends empty API group for core resources', async () => { render() + selectCluster() const resourceSelect = screen.getByTestId('can-i-resource') fireEvent.change(resourceSelect, { target: { value: 'pods' } }) @@ -652,6 +681,7 @@ describe('CanIChecker — API Group Handling', () => { it('allows overriding automatic API group selection', async () => { render() + selectCluster() const resourceSelect = screen.getByTestId('can-i-resource') fireEvent.change(resourceSelect, { target: { value: 'deployments' } }) From c8efc7c99505af3c85bfd069b7529309e1413a81 Mon Sep 17 00:00:00 2001 From: Scanner Bot Date: Mon, 20 Jul 2026 02:07:35 -0400 Subject: [PATCH 2/3] chore: retrigger CI Signed-off-by: Scanner Bot From 7330866d81bc562d60287bb686ee82697c7ba1a0 Mon Sep 17 00:00:00 2001 From: Copilot Date: Mon, 20 Jul 2026 03:10:58 -0400 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=8C=B1=20fix:=20remove=20unused=20aft?= =?UTF-8?q?erEach=20import=20from=20CanIChecker=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The afterEach block that restored mockClusters was removed in a prior commit, leaving afterEach imported but unused — triggering @typescript-eslint/no-unused-vars in the lint baseline check. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot --- web/src/components/rbac/__tests__/CanIChecker.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/rbac/__tests__/CanIChecker.test.tsx b/web/src/components/rbac/__tests__/CanIChecker.test.tsx index d6c541647c..95cfaa7a8c 100644 --- a/web/src/components/rbac/__tests__/CanIChecker.test.tsx +++ b/web/src/components/rbac/__tests__/CanIChecker.test.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { describe, it, expect, vi, beforeEach } from 'vitest' import { render, screen, fireEvent, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { CanIChecker } from '../CanIChecker'