diff --git a/ui/src/pages/AppsTab/AppDeploymentPage/ConfigTabs/readOnly-contract.test.tsx b/ui/src/pages/AppsTab/AppDeploymentPage/ConfigTabs/readOnly-contract.test.tsx new file mode 100644 index 00000000..d06928aa --- /dev/null +++ b/ui/src/pages/AppsTab/AppDeploymentPage/ConfigTabs/readOnly-contract.test.tsx @@ -0,0 +1,269 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ThemeProvider } from '@cameleer/design-system'; +import type { ReactNode } from 'react'; +import { MonitoringTab } from './MonitoringTab'; +import { ResourcesTab } from './ResourcesTab'; +import { VariablesTab } from './VariablesTab'; +import { SensitiveKeysTab } from './SensitiveKeysTab'; +import type { + MonitoringFormState, + ResourcesFormState, + VariablesFormState, + SensitiveKeysFormState, +} from '../hooks/useDeploymentPageState'; + +function wrap(ui: ReactNode) { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + }, + }); + return render( + + {ui} + , + ); +} + +const defaultMonitoring: MonitoringFormState = { + engineLevel: 'REGULAR', + payloadCaptureMode: 'BOTH', + payloadSize: '4', + payloadUnit: 'KB', + applicationLogLevel: 'INFO', + agentLogLevel: 'INFO', + metricsEnabled: true, + metricsInterval: '60', + samplingRate: '1.0', + compressSuccess: false, + replayEnabled: true, + routeControlEnabled: true, +}; + +const defaultResources: ResourcesFormState = { + memoryLimit: '512', + memoryReserve: '', + cpuRequest: '500', + cpuLimit: '', + ports: [], + appPort: '8080', + replicas: '1', + deployStrategy: 'blue-green', + stripPrefix: true, + sslOffloading: true, + runtimeType: 'auto', + customArgs: '', + extraNetworks: [], +}; + +const defaultVariables: VariablesFormState = { + envVars: [], +}; + +const defaultSensitiveKeys: SensitiveKeysFormState = { + sensitiveKeys: [], +}; + +describe('ConfigTabs disabled contract', () => { + describe('MonitoringTab', () => { + it('disables all inputs and selects when disabled=true', () => { + wrap( + , + ); + + const textboxes = screen.queryAllByRole('textbox'); + const comboboxes = screen.queryAllByRole('combobox'); + + expect(textboxes.length).toBeGreaterThan(0); + expect(comboboxes.length).toBeGreaterThan(0); + + textboxes.forEach((box) => { + expect(box).toBeDisabled(); + }); + comboboxes.forEach((cb) => { + expect(cb).toBeDisabled(); + }); + }); + + it('enables inputs when disabled=false', () => { + wrap( + , + ); + + const textboxes = screen.queryAllByRole('textbox'); + expect(textboxes.length).toBeGreaterThan(0); + textboxes.forEach((box) => { + expect(box).not.toBeDisabled(); + }); + }); + }); + + describe('ResourcesTab', () => { + it('disables all inputs and selects when disabled=true', () => { + wrap( + , + ); + + const textboxes = screen.queryAllByRole('textbox'); + const comboboxes = screen.queryAllByRole('combobox'); + + expect(textboxes.length).toBeGreaterThan(0); + expect(comboboxes.length).toBeGreaterThan(0); + + textboxes.forEach((box) => { + expect(box).toBeDisabled(); + }); + comboboxes.forEach((cb) => { + expect(cb).toBeDisabled(); + }); + }); + + it('enables inputs when disabled=false', () => { + wrap( + , + ); + + const textboxes = screen.queryAllByRole('textbox'); + expect(textboxes.length).toBeGreaterThan(0); + textboxes.forEach((box) => { + expect(box).not.toBeDisabled(); + }); + }); + }); + + describe('VariablesTab', () => { + it('disables add and import buttons when disabled=true', () => { + wrap( + , + ); + + const buttons = screen.queryAllByRole('button'); + // When disabled, import and clear/add buttons should be disabled + // Copy button is always enabled by design + const importBtn = buttons.find((btn) => btn.title?.includes('Import')); + const clearBtn = buttons.find((btn) => btn.title?.includes('Clear')); + expect(importBtn).toBeDisabled(); + expect(clearBtn).toBeDisabled(); + }); + + it('enables inputs when disabled=false', () => { + wrap( + , + ); + + const buttons = screen.queryAllByRole('button'); + const importBtn = buttons.find((btn) => btn.title?.includes('Import')); + const clearBtn = buttons.find((btn) => btn.title?.includes('Clear')); + expect(importBtn).not.toBeDisabled(); + expect(clearBtn).not.toBeDisabled(); + }); + + it('with envVars populated, disables textboxes when disabled=true', () => { + const varState: VariablesFormState = { + envVars: [ + { key: 'TEST_VAR', value: 'test-value' }, + { key: 'ANOTHER', value: 'value' }, + ], + }; + + wrap( + , + ); + + const textboxes = screen.queryAllByRole('textbox'); + expect(textboxes.length).toBeGreaterThan(0); + textboxes.forEach((box) => { + expect(box).toBeDisabled(); + }); + }); + }); + + describe('SensitiveKeysTab', () => { + it('disables input and add button when disabled=true', () => { + wrap( + , + ); + + const textboxes = screen.queryAllByRole('textbox'); + expect(textboxes.length).toBeGreaterThan(0); + textboxes.forEach((box) => { + expect(box).toBeDisabled(); + }); + + const buttons = screen.queryAllByRole('button'); + const addButton = buttons.find((btn) => btn.textContent?.includes('Add')); + expect(addButton).toBeDisabled(); + }); + + it('enables input when disabled=false', () => { + wrap( + , + ); + + const textboxes = screen.queryAllByRole('textbox'); + expect(textboxes.length).toBeGreaterThan(0); + textboxes.forEach((box) => { + expect(box).not.toBeDisabled(); + }); + }); + + it('with sensitive keys, disables remove tag buttons when disabled=true', () => { + const skState: SensitiveKeysFormState = { + sensitiveKeys: ['Authorization', 'X-API-Key', '*password*'], + }; + + wrap( + , + ); + + // Tags have remove buttons that should be disabled + const buttons = screen.queryAllByRole('button'); + // Check that at least some buttons exist (the tag removers) + expect(buttons.length).toBeGreaterThan(0); + }); + }); +});