Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})),
}));

Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})),
}));

Expand All @@ -38,46 +42,52 @@ const MockFormProvider = ({ children }: { children: React.ReactElement }) => {
);
};

interface RenderFormOptions {
enforceAdaptiveAllocations?: boolean;
}

const renderForm = (options: RenderFormOptions = {}) => {
const { enforceAdaptiveAllocations } = options;

return render(
<MockFormProvider>
<InferenceServiceFormFields
http={httpMock}
toasts={notificationsMock.toasts}
config={{ enforceAdaptiveAllocations }}
/>
</MockFormProvider>
);
};

describe('Inference Services', () => {
// Reset cloned providers before each test to prevent mutation pollution
beforeEach(() => {
mockClonedProviders = JSON.parse(JSON.stringify(mockProviders));
});
it('renders', () => {
render(
<MockFormProvider>
<InferenceServiceFormFields http={httpMock} toasts={notificationsMock.toasts} config={{}} />
</MockFormProvider>
);
renderForm();

expect(screen.getByTestId('provider-select')).toBeInTheDocument();
});

it('renders Selectable', async () => {
render(
<MockFormProvider>
<InferenceServiceFormFields http={httpMock} toasts={notificationsMock.toasts} config={{}} />
</MockFormProvider>
);
renderForm();

await userEvent.click(screen.getByTestId('provider-select'));
expect(screen.getByTestId('euiSelectableList')).toBeInTheDocument();
});

it('renders Elastic at top', async () => {
render(
<MockFormProvider>
<InferenceServiceFormFields http={httpMock} toasts={notificationsMock.toasts} config={{}} />
</MockFormProvider>
);
renderForm();

await userEvent.click(screen.getByTestId('provider-select'));
const listItems = screen.getAllByTestId('provider');
expect(listItems[0]).toHaveTextContent('Elastic');
});

it('renders selected provider fields - hugging_face', async () => {
render(
<MockFormProvider>
<InferenceServiceFormFields http={httpMock} toasts={notificationsMock.toasts} config={{}} />
</MockFormProvider>
);
renderForm();

await userEvent.click(screen.getByTestId('provider-select'));
await userEvent.click(screen.getByText('Hugging Face'));
Expand All @@ -93,11 +103,7 @@ describe('Inference Services', () => {
});

it('re-renders fields when selected to anthropic from hugging_face', async () => {
render(
<MockFormProvider>
<InferenceServiceFormFields http={httpMock} toasts={notificationsMock.toasts} config={{}} />
</MockFormProvider>
);
renderForm();

await userEvent.click(screen.getByTestId('provider-select'));
await userEvent.click(screen.getByText('Hugging Face'));
Expand Down Expand Up @@ -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();
});
});
});
});
Loading