diff --git a/x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common/src/components/inference_flyout_wrapper.test.tsx b/x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common/src/components/inference_flyout_wrapper.test.tsx
index 8e5912c7d4942..b8b86acf6a0f0 100644
--- a/x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common/src/components/inference_flyout_wrapper.test.tsx
+++ b/x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common/src/components/inference_flyout_wrapper.test.tsx
@@ -15,14 +15,19 @@ import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
import { InferenceFlyoutWrapper } from './inference_flyout_wrapper';
import { QueryClient, QueryClientProvider } from '@kbn/react-query';
import { mockProviders } from '../utils/mock_providers';
+import type { InferenceProvider } from '../types/types';
const mockMutationFn = jest.fn();
const httpMock = httpServiceMock.createStartContract();
const notificationsMock = notificationServiceMock.createStartContract();
+// Create a stable cloned copy for each test to prevent mutations from affecting other tests
+// Note: Variable must be prefixed with 'mock' to be allowed in jest.mock()
+let mockClonedProviders: InferenceProvider[];
+
jest.mock('../hooks/use_providers', () => ({
useProviders: jest.fn(() => ({
- data: mockProviders,
+ data: mockClonedProviders,
})),
}));
@@ -63,6 +68,8 @@ describe('InferenceFlyout', () => {
};
beforeEach(async () => {
jest.clearAllMocks();
+ // Reset cloned providers before each test to prevent mutation pollution
+ mockClonedProviders = JSON.parse(JSON.stringify(mockProviders));
});
it('renders', () => {
@@ -198,4 +205,86 @@ describe('InferenceFlyout', () => {
renderComponent({ isEdit: true, inferenceEndpoint: mockEndpoint });
expect(screen.getByTestId('num_allocations-number')).toBeEnabled();
});
+
+ // Note: UI visibility tests for serverless adaptive allocations are in inference_service_form_fields.test.tsx
+ // This file focuses on integration tests: form submission, deserialization, and cross-provider behavior
+ describe('Serverless adaptive allocations', () => {
+ it('does not affect other providers like Hugging Face', async () => {
+ renderComponent({ enforceAdaptiveAllocations: true });
+
+ await userEvent.click(screen.getByTestId('provider-select'));
+ await userEvent.click(screen.getByText('Hugging Face'));
+
+ expect(screen.getByTestId('provider-select')).toHaveValue('Hugging Face');
+ // Hugging Face fields should be visible as normal
+ expect(screen.getByTestId('api_key-password')).toBeInTheDocument();
+ expect(screen.getByTestId('url-input')).toBeInTheDocument();
+ // max_number_of_allocations should not be visible for non-elasticsearch providers
+ expect(screen.queryByTestId('max_number_of_allocations-number')).not.toBeInTheDocument();
+ });
+
+ it('submits form with adaptive_allocations config when max_number_of_allocations is set', async () => {
+ renderComponent({ enforceAdaptiveAllocations: true });
+
+ await userEvent.click(screen.getByTestId('provider-select'));
+ await userEvent.click(screen.getByText('Elasticsearch'));
+
+ // Set max allocations
+ const maxAllocationsInput = screen.getByTestId('max_number_of_allocations-number');
+ await userEvent.clear(maxAllocationsInput);
+ await userEvent.type(maxAllocationsInput, '10');
+
+ await userEvent.click(screen.getByTestId('inference-endpoint-submit-button'));
+
+ expect(mockMutationFn).toHaveBeenCalledWith(
+ expect.objectContaining({
+ config: expect.objectContaining({
+ provider: 'elasticsearch',
+ providerConfig: expect.objectContaining({
+ adaptive_allocations: expect.objectContaining({
+ enabled: true,
+ min_number_of_allocations: 0,
+ max_number_of_allocations: 10,
+ }),
+ num_threads: 1,
+ }),
+ }),
+ }),
+ false
+ );
+ });
+
+ describe('edit mode with adaptive allocations', () => {
+ it('deserializes adaptive_allocations.max_number_of_allocations for display in serverless', () => {
+ const mockEndpoint = {
+ config: {
+ inferenceId: 'test-id',
+ provider: 'elasticsearch',
+ taskType: 'text_embedding',
+ providerConfig: {
+ model_id: '.elser_model_2',
+ adaptive_allocations: {
+ enabled: true,
+ min_number_of_allocations: 0,
+ max_number_of_allocations: 5,
+ },
+ },
+ },
+ secrets: {
+ providerSecrets: {},
+ },
+ };
+
+ renderComponent({
+ isEdit: true,
+ enforceAdaptiveAllocations: true,
+ inferenceEndpoint: mockEndpoint,
+ });
+
+ // max_number_of_allocations should be shown with the deserialized value
+ expect(screen.getByTestId('max_number_of_allocations-number')).toBeInTheDocument();
+ expect(screen.getByTestId('max_number_of_allocations-number')).toHaveValue(5);
+ });
+ });
+ });
});
diff --git a/x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common/src/components/inference_service_form_fields.test.tsx b/x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common/src/components/inference_service_form_fields.test.tsx
index 0b6cb737a2ae0..5db3c41b14ddc 100644
--- a/x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common/src/components/inference_service_form_fields.test.tsx
+++ b/x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common/src/components/inference_service_form_fields.test.tsx
@@ -19,9 +19,13 @@ import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
import { mockProviders } from '../utils/mock_providers';
import type { InferenceProvider } from '../types/types';
+// Create a stable cloned copy for each test suite to prevent mutations from affecting other tests
+// Note: Variable must be prefixed with 'mock' to be allowed in jest.mock()
+let mockClonedProviders: InferenceProvider[];
+
jest.mock('../hooks/use_providers', () => ({
useProviders: jest.fn(() => ({
- data: mockProviders,
+ data: mockClonedProviders,
})),
}));
@@ -38,34 +42,44 @@ const MockFormProvider = ({ children }: { children: React.ReactElement }) => {
);
};
+interface RenderFormOptions {
+ enforceAdaptiveAllocations?: boolean;
+}
+
+const renderForm = (options: RenderFormOptions = {}) => {
+ const { enforceAdaptiveAllocations } = options;
+
+ return render(
+
+
+
+ );
+};
+
describe('Inference Services', () => {
+ // Reset cloned providers before each test to prevent mutation pollution
+ beforeEach(() => {
+ mockClonedProviders = JSON.parse(JSON.stringify(mockProviders));
+ });
it('renders', () => {
- render(
-
-
-
- );
+ renderForm();
expect(screen.getByTestId('provider-select')).toBeInTheDocument();
});
it('renders Selectable', async () => {
- render(
-
-
-
- );
+ renderForm();
await userEvent.click(screen.getByTestId('provider-select'));
expect(screen.getByTestId('euiSelectableList')).toBeInTheDocument();
});
it('renders Elastic at top', async () => {
- render(
-
-
-
- );
+ renderForm();
await userEvent.click(screen.getByTestId('provider-select'));
const listItems = screen.getAllByTestId('provider');
@@ -73,11 +87,7 @@ describe('Inference Services', () => {
});
it('renders selected provider fields - hugging_face', async () => {
- render(
-
-
-
- );
+ renderForm();
await userEvent.click(screen.getByTestId('provider-select'));
await userEvent.click(screen.getByText('Hugging Face'));
@@ -93,11 +103,7 @@ describe('Inference Services', () => {
});
it('re-renders fields when selected to anthropic from hugging_face', async () => {
- render(
-
-
-
- );
+ renderForm();
await userEvent.click(screen.getByTestId('provider-select'));
await userEvent.click(screen.getByText('Hugging Face'));
@@ -130,4 +136,122 @@ describe('Inference Services', () => {
expect(isProviderForSolutions('security', provider)).toBe(false);
});
});
+
+ describe('Serverless adaptive allocations', () => {
+ describe('when enforceAdaptiveAllocations is true (serverless)', () => {
+ it('shows max_number_of_allocations field for Elasticsearch provider', async () => {
+ renderForm({ enforceAdaptiveAllocations: true });
+
+ await userEvent.click(screen.getByTestId('provider-select'));
+ await userEvent.click(screen.getByText('Elasticsearch'));
+
+ expect(screen.getByTestId('provider-select')).toHaveValue('Elasticsearch');
+ // max_number_of_allocations should be visible in serverless
+ expect(screen.getByTestId('max_number_of_allocations-number')).toBeInTheDocument();
+ });
+
+ it('hides num_allocations field for Elasticsearch provider', async () => {
+ renderForm({ enforceAdaptiveAllocations: true });
+
+ await userEvent.click(screen.getByTestId('provider-select'));
+ await userEvent.click(screen.getByText('Elasticsearch'));
+
+ expect(screen.getByTestId('provider-select')).toHaveValue('Elasticsearch');
+ // num_allocations should be hidden in serverless
+ expect(screen.queryByTestId('num_allocations-number')).not.toBeInTheDocument();
+ });
+
+ it('hides num_threads field for Elasticsearch provider', async () => {
+ renderForm({ enforceAdaptiveAllocations: true });
+
+ await userEvent.click(screen.getByTestId('provider-select'));
+ await userEvent.click(screen.getByText('Elasticsearch'));
+
+ expect(screen.getByTestId('provider-select')).toHaveValue('Elasticsearch');
+ // num_threads should be hidden in serverless
+ expect(screen.queryByTestId('num_threads-number')).not.toBeInTheDocument();
+ });
+
+ it('shows adaptive resources title for Elasticsearch provider', async () => {
+ renderForm({ enforceAdaptiveAllocations: true });
+
+ await userEvent.click(screen.getByTestId('provider-select'));
+ await userEvent.click(screen.getByText('Elasticsearch'));
+
+ expect(screen.getByTestId('provider-select')).toHaveValue('Elasticsearch');
+ // Adaptive resources title should be shown in serverless
+ expect(screen.getByTestId('maxNumberOfAllocationsDetailsLabel')).toBeInTheDocument();
+ });
+ });
+
+ describe('when enforceAdaptiveAllocations is false (non-serverless)', () => {
+ it('shows num_allocations field for Elasticsearch provider', async () => {
+ renderForm({ enforceAdaptiveAllocations: false });
+
+ await userEvent.click(screen.getByTestId('provider-select'));
+ await userEvent.click(screen.getByText('Elasticsearch'));
+
+ expect(screen.getByTestId('provider-select')).toHaveValue('Elasticsearch');
+ // num_allocations should be visible in non-serverless
+ expect(screen.getByTestId('num_allocations-number')).toBeInTheDocument();
+ });
+
+ it('shows num_threads field for Elasticsearch provider', async () => {
+ renderForm({ enforceAdaptiveAllocations: false });
+
+ await userEvent.click(screen.getByTestId('provider-select'));
+ await userEvent.click(screen.getByText('Elasticsearch'));
+
+ expect(screen.getByTestId('provider-select')).toHaveValue('Elasticsearch');
+ // num_threads should be visible in non-serverless
+ expect(screen.getByTestId('num_threads-number')).toBeInTheDocument();
+ });
+
+ it('does not show max_number_of_allocations field for Elasticsearch provider', async () => {
+ renderForm({ enforceAdaptiveAllocations: false });
+
+ await userEvent.click(screen.getByTestId('provider-select'));
+ await userEvent.click(screen.getByText('Elasticsearch'));
+
+ expect(screen.getByTestId('provider-select')).toHaveValue('Elasticsearch');
+ // max_number_of_allocations should NOT be visible in non-serverless
+ expect(screen.queryByTestId('max_number_of_allocations-number')).not.toBeInTheDocument();
+ });
+
+ it('does not show adaptive resources title for Elasticsearch provider', async () => {
+ renderForm({ enforceAdaptiveAllocations: false });
+
+ await userEvent.click(screen.getByTestId('provider-select'));
+ await userEvent.click(screen.getByText('Elasticsearch'));
+
+ expect(screen.getByTestId('provider-select')).toHaveValue('Elasticsearch');
+ // Adaptive resources title should NOT be shown in non-serverless
+ expect(screen.queryByTestId('maxNumberOfAllocationsDetailsLabel')).not.toBeInTheDocument();
+ });
+ });
+
+ describe('when enforceAdaptiveAllocations is not provided (defaults to false)', () => {
+ it('shows num_allocations field for Elasticsearch provider', async () => {
+ renderForm();
+
+ await userEvent.click(screen.getByTestId('provider-select'));
+ await userEvent.click(screen.getByText('Elasticsearch'));
+
+ expect(screen.getByTestId('provider-select')).toHaveValue('Elasticsearch');
+ // num_allocations should be visible when enforceAdaptiveAllocations is not set
+ expect(screen.getByTestId('num_allocations-number')).toBeInTheDocument();
+ });
+
+ it('does not show max_number_of_allocations field for Elasticsearch provider', async () => {
+ renderForm();
+
+ await userEvent.click(screen.getByTestId('provider-select'));
+ await userEvent.click(screen.getByText('Elasticsearch'));
+
+ expect(screen.getByTestId('provider-select')).toHaveValue('Elasticsearch');
+ // max_number_of_allocations should NOT be visible when enforceAdaptiveAllocations is not set
+ expect(screen.queryByTestId('max_number_of_allocations-number')).not.toBeInTheDocument();
+ });
+ });
+ });
});