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);
+ });
+ });
+});